Хочу показать простой пример, как просто с помощью стандартных средств подгружать яваскриптовские скрипты и работать с ними. Для простоты эксперимента пусть в скриптах будут опеределяться две функции:
Name() — возвращает имя скрипта, которое могло бы в реальной игре быть именем бота.
Do(x, y) — некая функция, которая принимает два числовых аргумента, что-то делает и возвращает результат. В реальной игре она могла бы отвечать за поведение бота.
Код скриптов.
Dummie.js
Schoolboy.js
Genius.js
Как видим, имена разные, поведение разное. Также видим, что это не совсем JavaScript, это JScript.NET, что добавляет возможность юзать из скриптов сборки фреймворка (в Dummie.js используется генератор случайных чисел из пространства имён System).
Положим скрипты в папку Scripts.
Теперь пишем код на шарпе.
Ничего сложного нет. Выбираем все файлы из директории Scripts, которые имеют расширение ".js", компилируем каждый файл в сборку в памяти, выбираем из этих сборок классы, находящиеся в пространстве имён «Script» (в скриптах пространство имён задаётся как пакет), создаём экземпляры этих классов и кладём их в список.
Linq во всей красе. То, что на C++ потребует уйму кода, на C# делается вот так просто.
Выбираем из списка по очереди созданные нами на предыдущем шаге объекты и дёргаем методы Name() и Do(). Объекты динамические, чтобы не возиться с рефлекшеном. По-хорошему, тут стоит обрабатывать ситуации, когда этих методов в скриптах не окажется, но я решил не загромождать код.
Запускаем программу, получаем что-то вроде вот этого:
Вот и всё. Элементарный скриптовой движок за пять минут.
Name() — возвращает имя скрипта, которое могло бы в реальной игре быть именем бота.
Do(x, y) — некая функция, которая принимает два числовых аргумента, что-то делает и возвращает результат. В реальной игре она могла бы отвечать за поведение бота.
Код скриптов.
Dummie.js
import System;
package Script
{
class Dummie
{
function Name()
{
return "Dummie";
}
function Do(x, y)
{
var rnd = new Random(DateTime.Now.Millisecond);
return rnd.Next();
}
}
}
Schoolboy.js
package Script
{
class Schoolboy
{
function Name()
{
return "Schoolboy";
}
function Do(x, y)
{
return x + y;
}
}
}
Genius.js
package Script
{
class Genius
{
function Name()
{
return "Genius";
}
function Do(x, y)
{
return x * y;
}
}
}
Как видим, имена разные, поведение разное. Также видим, что это не совсем JavaScript, это JScript.NET, что добавляет возможность юзать из скриптов сборки фреймворка (в Dummie.js используется генератор случайных чисел из пространства имён System).
Положим скрипты в папку Scripts.
Теперь пишем код на шарпе.
const string scriptsDirectory = "Scripts";
var list = (from file in Directory.GetFiles(scriptsDirectory, "*.js")
let codeProvider = new Microsoft.JScript.JScriptCodeProvider()
let cp = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true,
TreatWarningsAsErrors = true
}
select codeProvider.CompileAssemblyFromFile(cp, file) into results
select results.CompiledAssembly.GetTypes() into types
from t in types
where t.IsClass && t.Namespace == "Script"
select t into solver
select Activator.CreateInstance(solver)).ToList();
Ничего сложного нет. Выбираем все файлы из директории Scripts, которые имеют расширение ".js", компилируем каждый файл в сборку в памяти, выбираем из этих сборок классы, находящиеся в пространстве имён «Script» (в скриптах пространство имён задаётся как пакет), создаём экземпляры этих классов и кладём их в список.
Linq во всей красе. То, что на C++ потребует уйму кода, на C# делается вот так просто.
foreach (dynamic obj in list)
Console.WriteLine("{0}: {1}", obj.Name(), obj.Do(1, 2));
Выбираем из списка по очереди созданные нами на предыдущем шаге объекты и дёргаем методы Name() и Do(). Объекты динамические, чтобы не возиться с рефлекшеном. По-хорошему, тут стоит обрабатывать ситуации, когда этих методов в скриптах не окажется, но я решил не загромождать код.
Запускаем программу, получаем что-то вроде вот этого:
Dummie: 890743837
Genius: 2
Schoolboy: 3
Вот и всё. Элементарный скриптовой движок за пять минут.
Комментарии (7)
Zagrebelion
05.11.2015 06:49+3в чём радость такого большого LINQ-выражения? Дебажить трудно, брекпоинт не поставить; только что похвастаться: «смотри, у меня весь foreach цикл — две строки».
NightmareZ
05.11.2015 06:52-1Но ведь на любом функциональном языке подобный код выглядел бы похожим образом. Разве нет?
IaIojek
05.11.2015 10:39Во-первых, Вы засоряете память сборками. От такого решения пропадает основной плюс скриптовых языков — подгрузил, выполнил, забыл. С тем же успехом можно и C# компилировать.
Во-вторых, Вы описали как достоинство возможность использовать типы из пространства имен System. Хоть pure-dotnet движков JavaScript и немного (а живых и того меньше), но каждый из них так умеет. И они не имеют описанной выше проблемы с памятью.
Посмотрите на Jint, к примеру
lair
Ну и чем это отличается от любого другого код-провайдера под .net?
(а использовали бы общий интерфейс — и dynamic бы не понадобился)
NightmareZ
Ничем. Но я думаю, не все знают, как это работает и что такое существует. Может кому пригодится.
lair
Может, стоило рассказать (а) что это такое и (б) как это работает?