Модульная система

Модули позволяют расширять функционал скриптов, регистрируя C# функции, классы и значения для использования в скриптовом коде.

Основы

Что такое модуль?

Модуль — это набор функций и классов, которые становятся доступными в скриптах. Например, модуль logging предоставляет функции для логирования:

-- В Lua скрипте
logging.info("Игра запущена")
logging.error("Произошла ошибка")

Встроенные модули

LoggingModule

Модуль для логирования из скриптов:

using IgdrasilEngine.Engine.Scripting.Modules;

var logger = /* ваш ILogger */;
var module = new LoggingModule<LuaModuleInterface>(logger);
engine.RegisterModule(module);

Доступные функции в скриптах:

logging.info("Информационное сообщение")
logging.debug("Отладочное сообщение")
logging.warning("Предупреждение")
logging.error("Ошибка")
logging.fatal("Критическая ошибка")
logging.wtf("Что-то пошло не так")

Создание собственных модулей

Базовый модуль

using IgdrasilEngine.Engine.Scripting;

public class MathModule : Module<LuaModuleInterface>
{
    public override string Name => "math_ext";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        // Регистрация функций
        moduleInterface.RegisterFunction("square", (double x) => x * x);
        moduleInterface.RegisterFunction("cube", (double x) => x * x * x);
        
        // Регистрация значений
        moduleInterface.RegisterValue("PI", Math.PI);
        moduleInterface.RegisterValue("E", Math.E);
    }
}

// Использование
engine.RegisterModule(new MathModule());

В Lua:

print(math_ext.square(5))  -- 25
print(math_ext.cube(3))    -- 27
print(math_ext.PI)         -- 3.14159...

Модуль с классами

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    
    public void Attack(string target)
    {
        Console.WriteLine($"{Name} атакует {target}!");
    }
}

public class GameModule : Module<LuaModuleInterface>
{
    public override string Name => "game";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        // Регистрация класса
        moduleInterface.RegisterClass("Player", typeof(Player));
        
        // Регистрация фабричной функции
        moduleInterface.RegisterFunction("createPlayer", (string name, int health) =>
            new Player { Name = name, Health = health }
        );
    }
}

В Lua:

local player = game.createPlayer("Hero", 100)
player.Name = "Super Hero"
player:Attack("Dragon")

Модуль с перечислениями

public enum GameState
{
    Menu,
    Playing,
    Paused,
    GameOver
}

public class StateModule : Module<LuaModuleInterface>
{
    public override string Name => "states";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        // Регистрация перечисления
        moduleInterface.RegisterEnum("GameState", typeof(GameState));
    }
}

В Lua:

local currentState = states.GameState.Playing

Жизненный цикл модуля

Расширенная структура модуля

public class GameLoopModule : Module<LuaModuleInterface>
{
    public override string Name => "gameloop";
    public override string Version => "1.0.0";
    public override string? Description => "Модуль игрового цикла";
    public override int InitializationOrder => 10; // Меньше = раньше инициализируется
    
    private float _totalTime = 0;
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        moduleInterface.RegisterFunction("getTotalTime", () => _totalTime);
        moduleInterface.RegisterFunction("reset", () => _totalTime = 0);
    }
    
    public override void OnInitialized()
    {
        Console.WriteLine("GameLoopModule инициализирован");
    }
    
    public override void Update(float deltaTime)
    {
        _totalTime += deltaTime;
    }
    
    public override void Shutdown()
    {
        Console.WriteLine($"GameLoopModule завершен после {_totalTime}s");
    }
    
    public override ModuleCompatibilityResult CheckCompatibility()
    {
        // Проверка совместимости
        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
            return ModuleCompatibilityResult.Incompatible("Работает только на Windows");
        
        return ModuleCompatibilityResult.Compatible;
    }
}

Использование Update

// В игровом цикле
var engine = new LuaEngine();
engine.RegisterModule(new GameLoopModule());

// Каждый кадр
while (isRunning)
{
    float deltaTime = CalculateDeltaTime();
    engine.Update(deltaTime);
    
    // Выполнение скриптов
    engine.Execute("print(gameloop.getTotalTime())");
}

Атрибут ScriptExposed

Для явного экспорта методов используйте атрибут ScriptExposedAttribute:

using IgdrasilEngine.Engine.Attributes;

public class AudioManager
{
    [ScriptExposed("playSound")]
    public void PlaySound(string soundName)
    {
        // Воспроизведение звука
    }
    
    [ScriptExposed("stopAll", Description = "Останавливает все звуки")]
    public void StopAllSounds()
    {
        // Остановка всех звуков
    }
    
    // Этот метод не будет экспортирован
    internal void InternalMethod() { }
}

Валидация типов

Используйте ITypeValidator для проверки типов:

public class SafeModule : Module<LuaModuleInterface>
{
    public override string Name => "safe";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        var validator = moduleInterface.GetTypeValidator();
        
        Action<string> myFunction = (text) => Console.WriteLine(text);
        
        // Проверка перед регистрацией
        var validationResult = validator?.ValidateMethod(myFunction);
        if (validationResult?.IsValid == true)
        {
            moduleInterface.RegisterFunction("print", myFunction);
        }
        else
        {
            Console.WriteLine($"Ошибка валидации: {validationResult?.ErrorMessage}");
        }
    }
}

Best Practices

  1. Именование: Используйте короткие, понятные имена для модулей
  2. Группировка: Объединяйте связанные функции в один модуль
  3. Версионирование: Указывайте версию для совместимости
  4. Документация: Добавляйте описания через атрибуты
  5. Производительность: Избегайте тяжелых операций в Update()
  6. Cleanup: Освобождайте ресурсы в Shutdown()

Примеры модулей

Модуль работы с файлами

public class FileModule : Module<LuaModuleInterface>
{
    public override string Name => "file";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        moduleInterface.RegisterFunction("read", (string path) => 
            File.ReadAllText(path));
        
        moduleInterface.RegisterFunction("write", (string path, string content) => 
            File.WriteAllText(path, content));
        
        moduleInterface.RegisterFunction("exists", (string path) => 
            File.Exists(path));
    }
}

Модуль HTTP-запросов

public class HttpModule : Module<LuaModuleInterface>
{
    private readonly HttpClient _client = new();
    
    public override string Name => "http";
    
    public override void Initialize(LuaModuleInterface moduleInterface)
    {
        moduleInterface.RegisterFunction("get", async (string url) =>
        {
            var response = await _client.GetAsync(url);
            return await response.Content.ReadAsStringAsync();
        });
    }
    
    public override void Shutdown()
    {
        _client.Dispose();
    }
}