Хочу показать простой пример, как просто с помощью стандартных средств подгружать яваскриптовские скрипты и работать с ними. Для простоты эксперимента пусть в скриптах будут опеределяться две функции:
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)


  1. lair
    05.11.2015 00:45
    +2

    Ну и чем это отличается от любого другого код-провайдера под .net?

    (а использовали бы общий интерфейс — и dynamic бы не понадобился)


    1. NightmareZ
      05.11.2015 00:49
      +1

      Ничем. Но я думаю, не все знают, как это работает и что такое существует. Может кому пригодится.


      1. lair
        05.11.2015 00:50
        +4

        Может, стоило рассказать (а) что это такое и (б) как это работает?


  1. Zagrebelion
    05.11.2015 06:49
    +3

    в чём радость такого большого LINQ-выражения? Дебажить трудно, брекпоинт не поставить; только что похвастаться: «смотри, у меня весь foreach цикл — две строки».


    1. NightmareZ
      05.11.2015 06:52
      -1

      Но ведь на любом функциональном языке подобный код выглядел бы похожим образом. Разве нет?


  1. IaIojek
    05.11.2015 10:39

    Во-первых, Вы засоряете память сборками. От такого решения пропадает основной плюс скриптовых языков — подгрузил, выполнил, забыл. С тем же успехом можно и C# компилировать.
    Во-вторых, Вы описали как достоинство возможность использовать типы из пространства имен System. Хоть pure-dotnet движков JavaScript и немного (а живых и того меньше), но каждый из них так умеет. И они не имеют описанной выше проблемы с памятью.

    Посмотрите на Jint, к примеру


  1. impwx
    05.11.2015 14:24

    Обязательный комментарий про свой велосипед из той же области