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

Узнайте, как использовать обертки классов для более чистых API плагинов, основанных на объектно-ориентированном программировании, в Lua.

Классы в Plugify предоставляют объектно-ориентированный способ работы с ресурсами плагинов, обеспечивая автоматическое управление жизненным циклом и более чистые API. Вместо ручного управления дескрипторами и вызова функций с явными указателями, вы можете использовать интуитивные интерфейсы на основе классов.

Зачем использовать классы?

Работа с API на основе дескрипторов в традиционном стиле C может быть многословной и подверженной ошибкам:

Без классов (стиль C)
local s2sdk = require("s2sdk")

-- Создать дескриптор KeyValues вручную
local kv_handle = s2sdk.Kv1Create("MyConfig")

-- Установить свойства, используя дескриптор
s2sdk.Kv1SetName(kv_handle, "ServerSettings")

-- Найти подключ
local subkey_handle = s2sdk.Kv1FindKey(kv_handle, "Players")

-- Легко забыть очистку!
s2sdk.Kv1Destroy(subkey_handle)
s2sdk.Kv1Destroy(kv_handle)

С классами тот же код становится намного чище и безопаснее:

С классами (стиль ООП)
local s2sdk = require("s2sdk")

-- Создать с использованием конструктора класса (Lua 5.4+ с <close>)
local kv <close> = s2sdk.KeyValues.new("MyConfig")

-- Использовать интуитивные методы
kv:SetName("ServerSettings")

-- Найти подключ - возвращает экземпляр KeyValues
local subkey <close> = kv:FindKey("Players")

-- Автоматическая очистка при выходе из области видимости!

Преимущества:

  • Более чистый синтаксис - Методы вместо функций с явными дескрипторами
  • Автоматическое управление ресурсами - Нет необходимости вручную вызывать функции уничтожения
  • Очистка на основе области видимости - <close> в Lua 5.4 обеспечивает детерминированную очистку ресурсов
  • Резервная очистка - Финализатор __gc в качестве страховки для старых версий Lua
  • Меньше ошибок - Труднее забыть очистку или перепутать дескрипторы
  • Идиоматичный API для Lua - Ощущается естественным для разработчиков на Lua

Как работают классы

Когда плагин определяет классы в своем манифесте, Plugify автоматически генерирует обертки классов Lua, которые:

  1. Оборачивают базовый дескриптор - Хранят необработанный указатель внутри
  2. Связывают методы - Преобразуют вызовы функций в вызовы методов с автоматической передачей дескриптора
  3. Управляют жизненным циклом - Автоматически вызывают деструктор, когда объект больше не нужен
  4. Предоставляют утилитарные методы - get(), release(), valid(), close(), reset() для управления дескриптором
  5. Поддерживают очистку на основе области видимости - Реализуют __close для переменных to-be-closed в Lua 5.4+
  6. Предоставляют резервную очистку - Реализуют __gc для финализации сборщиком мусора

Определение классов в вашем манифесте

Чтобы создать классы для вашего плагина, добавьте секцию classes в ваш манифест:

plugin.pplugin
{
  "name": "example_plugin",
  "version": "1.0.0",
  "language": "cpp",
  "methods": [
    {
      "name": "Kv1Create",
      "funcName": "Kv1Create",
      "paramTypes": [
        { "name": "setName", "type": "string" }
      ],
      "retType": { "type": "ptr64" }
    },
    {
      "name": "Kv1Destroy",
      "funcName": "Kv1Destroy",
      "paramTypes": [
        { "name": "kv", "type": "ptr64" }
      ],
      "retType": { "type": "void" }
    },
    {
      "name": "Kv1GetName",
      "funcName": "Kv1GetName",
      "paramTypes": [
        { "name": "kv", "type": "ptr64" }
      ],
      "retType": { "type": "string" }
    },
    {
      "name": "Kv1SetName",
      "funcName": "Kv1SetName",
      "paramTypes": [
        { "name": "kv", "type": "ptr64" },
        { "name": "name", "type": "string" }
      ],
      "retType": { "type": "void" }
    }
  ],
  "classes": [
    {
      "name": "KeyValues",
      "description": "RAII wrapper for KeyValues handle",
      "handleType": "ptr64",
      "invalidValue": "0",
      "constructors": ["Kv1Create"],
      "destructor": "Kv1Destroy",
      "bindings": [
        {
          "name": "GetName",
          "method": "Kv1GetName",
          "bindSelf": true
        },
        {
          "name": "SetName",
          "method": "Kv1SetName",
          "bindSelf": true
        }
      ]
    }
  ]
}

Объяснение ключевых полей:

  • name: Имя класса Lua (рекомендуется PascalCase)
  • handleType: Тип базового дескриптора (обычно ptr64 или ptr32)
  • invalidValue: Какое значение представляет недопустимый дескриптор (обычно "0" или "-1")
  • constructors: Массив имен методов, которые создают экземпляры
  • destructor: Имя метода, который очищает ресурсы (опционально)
  • bindings: Массив методов, доступных в классе
    • bindSelf: Если true, автоматически передает дескриптор в качестве первого параметра

Сгенерированный код Lua

Когда вы определяете классы в своем манифесте, Plugify генерирует обертки классов Lua с несколькими встроенными методами:

Сгенерированный класс (концептуально)
local KeyValues = {}
KeyValues.__type = "KeyValues"
KeyValues.__index = KeyValues

-- Конструктор
function KeyValues.new(...)
    local self = setmetatable({}, KeyValues)

    -- Сначала инициализировать в недопустимое состояние
    self._handle = 0  -- invalid_value
    self._owned = Ownership.BORROWED

    local args = {...}

    -- Конструкция прямого дескриптора
    -- Паттерн: KeyValues.new(handle_value, Ownership.OWNED)
    if #args >= 2 and Ownership.is(args[2]) then
        self._handle = args[1]
        self._owned = args[2]
        return self
    end

    -- Режим вызова конструктора
    local success, result = pcall(_plugin.Kv1Create, table.unpack(args))
    if success then
        self._handle = result
        self._owned = Ownership.OWNED
        return self
    else
        error(result)
    end
end

-- Метод close
function KeyValues:close()
    if not self._handle then
        return
    end

    if self._handle ~= 0 and self._owned == Ownership.OWNED then
        _plugin.Kv1Destroy(self._handle)
    end
    self._handle = 0
    self._owned = Ownership.BORROWED
end

-- Метаметод __close для переменных to-be-closed в Lua 5.4+
KeyValues.__close = function(self)
    self:close()
end

-- Метаметод __gc (финализатор сборщика мусора)
KeyValues.__gc = function(self)
    self:close()
end

-- Утилитарные методы
function KeyValues:get()
    if not self._handle then
        return 0
    end
    return self._handle
end

function KeyValues:release()
    if not self._handle then
        return 0
    end
    local tmp = self._handle
    self._handle = 0
    self._owned = Ownership.BORROWED
    return tmp
end

function KeyValues:reset()
    self:close()
end

function KeyValues:valid()
    if not self._handle then
        return false
    end
    return self._handle ~= 0
end

-- Связанные методы (с автоматической проверкой дескриптора)
function KeyValues:GetName()
    if not self._handle or self._handle == 0 then
        error("KeyValues handle is closed or not initialized")
    end
    return _plugin.Kv1GetName(self._handle)
end

function KeyValues:SetName(name)
    if not self._handle or self._handle == 0 then
        error("KeyValues handle is closed or not initialized")
    end
    _plugin.Kv1SetName(self._handle, name)
end

function KeyValues:FindKey(keyName)
    if not self._handle or self._handle == 0 then
        error("KeyValues handle is closed or not initialized")
    end
    local result = _plugin.Kv1FindKey(self._handle, keyName)
    -- Автоматически оборачивает возвращаемое значение на основе retAlias
    if result ~= 0 then
        return KeyValues.new(result, Ownership.OWNED)
    end
    return nil
end

function KeyValues:AddSubKey(subKey)
    if not self._handle or self._handle == 0 then
        error("KeyValues handle is closed or not initialized")
    end
    -- Автоматически освобождает владение от параметра subKey
    local handle = (type(subKey) == "table" and subKey.release) and subKey:release() or subKey
    _plugin.Kv1AddSubKey(self._handle, handle)
end

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

Каждый сгенерированный класс включает несколько утилитарных методов для управления дескриптором:

valid() - Проверка действительности дескриптора

Возвращает true, если дескриптор действителен (не равен invalidValue):

Использование valid()
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")

if kv:valid() then
    kv:SetName("ServerConfig")
    print("Handle is valid")
else
    print("Handle is invalid")
end

-- После release дескриптор становится недействительным
local handle = kv:release()
print(kv:valid())  -- false

get() - Доступ к необработанному дескриптору

Возвращает значение базового дескриптора. Используйте это, когда вам нужно передать необработанный дескриптор в функции в стиле C:

Использование get()
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")

-- Получить необработанный дескриптор
local raw_handle = kv:get()
print("Handle value: " .. tostring(raw_handle))

-- Передать в функцию, ожидающую необработанный дескриптор
some_c_function(kv:get())

release() - Передача владения

Освобождает владение дескриптором и возвращает его. После вызова release() экземпляр класса становится недействительным и не будет вызывать деструктор:

Использование release()
local s2sdk = require("s2sdk")

local function create_and_release()
    local kv = s2sdk.KeyValues.new("Config")
    kv:SetName("ServerConfig")

    -- Передать владение наружу
    local handle = kv:release()

    -- kv теперь недействителен, не будет очищать
    print(kv:valid())  -- false

    return handle
end

-- Теперь мы владеем дескриптором и должны очистить его вручную
local raw_handle = create_and_release()
-- ... использовать raw_handle ...
s2sdk.Kv1Destroy(raw_handle)  -- Требуется ручная очистка!

Используйте release(), когда вам нужно:

  • Передать владение другой системе
  • Сохранить дескриптор в долгоживущей структуре данных
  • Взаимодействовать с кодом в стиле C, который принимает владение

close() - Ручная очистка

Явно закрывает дескриптор и вызывает деструктор, если объект владеет им:

Использование close()
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")
kv:SetName("ServerConfig")

-- Явно закрыть дескриптор сейчас
kv:close()

-- Дескриптор теперь недействителен
print(kv:valid())  -- false

-- Методы вызовут ошибку
local success, err = pcall(function()
    kv:SetName("Test")
end)
if not success then
    print("Error: " .. err)  -- "KeyValues handle is closed"
end

Метод close():

  • Идемпотентный - Безопасно вызывать несколько раз
  • Вызывается автоматически - С помощью __close и __gc
  • Учитывает владение - Вызывает деструктор только если объект владеет дескриптором

reset() - Псевдоним для Close

Удобный метод, который вызывает close():

Использование reset()
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")
kv:SetName("ServerConfig")

-- Reset эквивалентен close
kv:reset()

print(kv:valid())  -- false

Управление ресурсами

Классы Lua, сгенерированные Plugify, поддерживают несколько паттернов очистки:

Очистка на основе области видимости (Lua 5.4+, рекомендуется)

Lua 5.4 представила переменные to-be-closed с использованием аннотации <close>. Это обеспечивает детерминированную очистку, аналогичную RAII в других языках:

Переменные to-be-closed (Lua 5.4+)
local s2sdk = require("s2sdk")

do
    local kv <close> = s2sdk.KeyValues.new("Config")
    kv:SetName("ServerConfig")
    kv:SetString("hostname", "My Server")
    -- ... использовать kv ...
    -- kv:close() автоматически вызывается при выходе из этой области видимости
end
-- Определенно очищается здесь!

Это рекомендуемый паттерн для Lua 5.4+, потому что:

  • Гарантированная очистка даже при возникновении ошибок
  • Четкая область жизни ресурса
  • Детерминированное время очистки
  • Идиоматично для современного кода Lua

Автоматическая очистка (все версии Lua)

Финализатор сборщика мусора (__gc) вызывается автоматически при сборке мусора объекта:

Очистка сборщиком мусора
local s2sdk = require("s2sdk")

local function process_config()
    local kv = s2sdk.KeyValues.new("Config")
    kv:SetName("ServerConfig")
    -- ... использовать kv ...
    -- kv будет уничтожен при сборке мусора
end

process_config()
-- kv в конечном итоге будет очищен GC

Ручная очистка

Для немедленной очистки ресурсов в любой версии Lua используйте close() явно:

Ручная очистка
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")
kv:SetName("ServerConfig")
-- ... использовать kv ...

-- Явно очистить сейчас
kv:close()
-- Дескриптор уничтожается немедленно

Случай без деструктора

Если в манифесте класс не имеет определенного деструктора, он действует как простая обертка без автоматической очистки:

Обертка без деструктора
local s2sdk = require("s2sdk")

-- Класс без деструктора - просто удобная обертка
local wrapper = s2sdk.SomeWrapper.new()

-- Все еще имеет утилитарные методы
if wrapper:valid() then
    local handle = wrapper:get()
end

-- Нет автоматической очистки - дескриптор сохраняется
-- Полезно для оберток без состояния или глобальных ресурсов

Работа с владением

Некоторые методы передают владение ресурсами. Манифест указывает это с помощью поля owner в paramAliases и retAlias.

Принятие владения (параметры метода)

Когда метод принимает владение ресурсом, вы должны передать его и не использовать после этого:

Передача владения
local s2sdk = require("s2sdk")

-- Создать родителя и ребенка
local parent = s2sdk.KeyValues.new("Parent")
local child = s2sdk.KeyValues.new("Child")

-- AddSubKey принимает владение child
parent:AddSubKey(child)

-- child теперь принадлежит parent
-- child:valid() может все еще быть true, но не используйте его!
-- parent обработает очистку

В манифесте это определяется как:

Владение в манифесте
{
  "name": "AddSubKey",
  "method": "Kv1AddSubKey",
  "bindSelf": true,
  "paramAliases": [
    {
      "name": "subKey",
      "owner": true  // Этот параметр принимает владение
    }
  ]
}

Лучшая практика: После передачи владения избегайте использования объекта:

Правильная обработка владения
local s2sdk = require("s2sdk")

local parent = s2sdk.KeyValues.new("Parent")
local child = s2sdk.KeyValues.new("Child")

-- Передать владение
parent:AddSubKey(child)

-- Освободить нашу ссылку, чтобы предотвратить случайное использование
local child_handle = child:release()  -- Теперь child:valid() == false

-- Использовать только через parent
local found = parent:FindKey("Child")

Возврат владения (возвращаемые значения)

Когда метод возвращает новый ресурс с владением:

Возврат владения
local s2sdk = require("s2sdk")

local parent = s2sdk.KeyValues.new("Parent")

-- FindKey возвращает НОВЫЙ KeyValues, которым мы владеем
local child = parent:FindKey("Settings")

if child and child:valid() then
    child:SetName("UpdatedSettings")
    -- Мы ответственны за жизненный цикл child
    -- Он будет автоматически очищен, когда выйдет из области видимости (Lua 5.4+)
    -- Или сборщиком мусора (все версии Lua)
end

В манифесте:

Возврат владения в манифесте
{
  "name": "FindKey",
  "method": "Kv1FindKey",
  "bindSelf": true,
  "retAlias": {
    "name": "KeyValues",
    "owner": true  // Вызывающий владеет возвращаемым объектом
  }
}

Невладеющие ссылки

Когда owner: false, метод возвращает ссылку без передачи владения:

Невладеющая ссылка
local s2sdk = require("s2sdk")

local parent = s2sdk.KeyValues.new("Parent")

-- GetFirstSubKey возвращает ссылку, parent все еще владеет ею
local child_ref = parent:GetFirstSubKey()

if child_ref and child_ref:valid() then
    -- Используйте ссылку, но НЕ закрывайте ее
    local name = child_ref:GetName()
    -- Не вызывайте child_ref:close() или child_ref:release()
    -- child_ref будет очищен parent
end

Жизненный цикл плагина и глобальные объекты

При использовании классов в плагинах вы должны быть осторожны с очисткой объектов во время выгрузки плагина. Финализаторы сборщика мусора Lua (__gc) вызываются при сборке объектов, но если объекты все еще живы, когда ваш плагин выгружается, их финализаторы могут быть вызваны после того, как код плагина больше не находится в памяти, что приводит к неопределенному поведению или сбоям.

Управление глобальными объектами

Если вы храните экземпляры классов в глобальных переменных или таблицах уровня модуля, вы должны явно очистить их в функции PluginEnd() вашего плагина:

plugin.lua
-- Глобальный объект - опасен, если не очищен!
local g_config = nil

function PluginStart()
    -- Создать глобальную конфигурацию
    g_config = s2sdk.KeyValues.new("GlobalConfig")
    g_config:SetName("ServerSettings")
    print("Plugin started with global config")
end

function PluginEnd()
    -- КРИТИЧНО: Очистить глобальные объекты перед выгрузкой плагина
    if g_config then
        g_config:close()
        g_config = nil
    end
    print("Plugin ended, global config cleaned up")
end

Безопасные паттерны

✅ Безопасно: Локальная область видимости с to-be-closed (Lua 5.4+)

Безопасный паттерн
function OnCommand(args)
    -- Локальный объект с <close> - автоматически очищается
    local kv <close> = s2sdk.KeyValues.new("TempConfig")
    kv:SetName("CommandConfig")
    -- ... использовать kv ...
    -- Автоматически уничтожается при выходе из области видимости - безопасно!
end

✅ Безопасно: Локальная область видимости с ручной очисткой

Безопасный паттерн
function OnCommand(args)
    local kv = s2sdk.KeyValues.new("TempConfig")
    kv:SetName("CommandConfig")
    -- ... использовать kv ...
    kv:close()  -- Явная очистка - безопасно!
end

✅ Безопасно: Переменная модуля с очисткой

Безопасный паттерн
local plugin_config = nil

function PluginStart()
    plugin_config = s2sdk.KeyValues.new("PluginConfig")
    plugin_config:SetName("Settings")
end

function PluginEnd()
    -- Очистить переменные модуля
    if plugin_config then
        plugin_config:close()
        plugin_config = nil
    end
end

❌ Небезопасно: Глобальная без очистки

Небезопасный паттерн
-- ОПАСНО: Глобальный объект
g_config = s2sdk.KeyValues.new("GlobalConfig")

function PluginStart()
    g_config:SetName("ServerSettings")
end

function PluginEnd()
    -- ОТСУТСТВУЕТ: Нет очистки!
    -- Финализатор g_config запустится после выгрузки плагина - СБОЙ!
end

❌ Небезопасно: Кэш уровня модуля без очистки

Небезопасный паттерн
-- ОПАСНО: Кэш уровня модуля
local kv_cache = {}

function CacheConfig(name)
    kv_cache[name] = s2sdk.KeyValues.new(name)
end

function PluginEnd()
    -- ОТСУТСТВУЕТ: Нет очистки кэша!
    -- Кэшированные объекты завершатся после выгрузки плагина - СБОЙ!
end

Контрольный список очистки

Перед выгрузкой вашего плагина (PluginEnd()), убедитесь:

  1. ✅ Все глобальные экземпляры классов явно закрыты или установлены в nil
  2. ✅ Все таблицы уровня модуля, содержащие экземпляры классов, очищены
  3. ✅ Не осталось ссылок на экземпляры классов в долгоживущих структурах данных
  4. ✅ Циклические ссылки разорваны (если есть)

Полный пример: Система конфигурации

Вот полный пример, показывающий, как использовать классы для системы конфигурации:

config_manager.lua
local s2sdk = require("s2sdk")

local ConfigManager = {}
ConfigManager.__index = ConfigManager

function ConfigManager.new(config_name)
    local self = setmetatable({}, ConfigManager)
    self.root = s2sdk.KeyValues.new(config_name)

    if not self.root:valid() then
        error("Failed to create config: " .. config_name)
    end

    return self
end

function ConfigManager:close()
    if self.root then
        self.root:close()
        self.root = nil
    end
end

-- Поддержка Lua 5.4+
ConfigManager.__close = function(self)
    self:close()
end

function ConfigManager:create_section(section_name)
    local section = s2sdk.KeyValues.new(section_name)

    if not section:valid() then
        return false
    end

    self.root:AddSubKey(section)
    return true
end

function ConfigManager:get_section(section_name)
    local section = self.root:FindKey(section_name)

    if section and section:valid() then
        return section
    end
    return nil
end

function ConfigManager:set_value(section_name, key, value)
    local section = self:get_section(section_name)

    if section then
        section:SetString(key, value)
        return true
    end
    return false
end

function ConfigManager:get_value(section_name, key, default)
    default = default or ""
    local section = self:get_section(section_name)

    if section then
        return section:GetString(key, default)
    end
    return default
end

function ConfigManager:save(filename)
    if not self.root:valid() then
        return false
    end
    return self.root:SaveToFile(filename)
end

function ConfigManager:load(filename)
    if not self.root:valid() then
        return false
    end
    return self.root:LoadFromFile(filename)
end

-- Использование (Lua 5.4+)
do
    local config <close> = ConfigManager.new("ServerConfig")
    config:create_section("Server")
    config:set_value("Server", "hostname", "My Server")
    config:set_value("Server", "maxplayers", "32")

    config:create_section("Game")
    config:set_value("Game", "mode", "competitive")

    config:save("config.kv")
    -- Автоматически очищается при выходе из области видимости
end

Лучшие практики

  1. Используйте <close> для Lua 5.4+ - Используйте переменные to-be-closed для гарантированной очистки
  2. Используйте close() для немедленной очистки - Более явно, чем полагаться на сборку мусора
  3. Очищайте глобальные объекты в PluginEnd() - Критично: Явно очищайте все глобальные или уровня модуля экземпляры классов перед выгрузкой плагина, чтобы избежать сбоев
  4. Предпочитайте локальную область видимости - Держите экземпляры классов в локальной области видимости, когда возможно, для автоматической очистки
  5. Проверяйте valid() для безопасности - Особенно после операций, которые могут завершиться неудачей
  6. Уважайте владение - Не используйте объекты после передачи владения
  7. Используйте release() экономно - Только когда вам нужен ручной контроль
  8. Избегайте смешивания стилей - Предпочитайте API классов необработанным функциям в стиле C
  9. Не полагайтесь на время __gc - Сборка мусора недетерминирована, используйте <close> или close() для немедленной очистки
  10. Обрабатывайте ошибки - Будьте готовы перехватывать ошибки от методов, вызванных на закрытых дескрипторах
  11. Проверяйте возвращаемые объекты - Методы могут возвращать nil при неудаче

Когда НЕ использовать классы

Классы предназначены для ресурсов, которым требуется управление жизненным циклом. Не определяйте классы для:

  • Функции-утилиты без состояния - Простые функции, которые не управляют ресурсами
  • Функции, возвращающие примитивные значения - Нет необходимости оборачивать простые геттеры
  • Одноразовые операции - Операции, которые не поддерживают состояние
  • Глобальные синглтоны - Ресурсы, которые живут в течение всего времени работы программы

Для этих случаев продолжайте использовать обычный API на основе функций.

Устранение неполадок

Ошибка "Failed to create X"

Если конструктор вызывает ошибку, базовая функция создания вернула недопустимый дескриптор:

Сбой конструктора дескриптора
local success, err = pcall(function()
    return s2sdk.KeyValues.new("Config")
end)

if not success then
    print("Failed to create KeyValues: " .. err)
    -- Обработать ошибку - возможно повторить или использовать конфигурацию по умолчанию
end

Использование закрытых или освобожденных объектов

Попытка использовать объект после вызова close() или release() вызовет ошибку:

Использование закрытого дескриптора
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")
kv:close()

-- Это вызовет ошибку
local success, err = pcall(function()
    kv:SetName("Test")
end)

if not success then
    print(err)  -- "KeyValues handle is closed"
end

-- Сначала проверьте valid(), чтобы избежать ошибок
if kv:valid() then
    kv:SetName("Test")
else
    print("Handle is closed!")
end

Все связанные методы автоматически проверяют дескриптор перед вызовом базовой функции C:

Автоматическая проверка
local s2sdk = require("s2sdk")

local kv = s2sdk.KeyValues.new("Config")
local handle = kv:release()

-- Любой вызов метода завершится неудачей
local success, err = pcall(function()
    return kv:GetName()
end)

if not success then
    print(err)  -- "KeyValues handle is closed"
end

Висячие ссылки

Будьте осторожны с невладеющими ссылками, когда владелец уничтожается:

Проблема висячей ссылки
local function get_child_ref()
    local parent = s2sdk.KeyValues.new("Parent")
    local child = parent:GetFirstSubKey()  -- Невладеющая ссылка
    return child  -- ПЛОХО: parent будет уничтожен!
end

-- Это опасно!
local child_ref = get_child_ref()
-- child_ref теперь указывает на уничтоженную память

-- Лучший подход:
local function get_child_owned()
    local parent = s2sdk.KeyValues.new("Parent")
    local child = parent:FindKey("Child")  -- Возвращает принадлежащий экземпляр
    -- Нужно держать parent живым или убедиться, что child используется до очистки parent
    return child  -- Безопасно, если FindKey возвращает принадлежащий и используется до GC
end

Сбои при выгрузке плагина

Если ваш плагин падает при выгрузке:

  1. Проверьте глобальные объекты - Убедитесь, что все глобальные экземпляры классов очищены в PluginEnd()
  2. Проверьте таблицы уровня модуля - Очистите все таблицы, содержащие экземпляры классов
  3. Добавьте явную очистку - Используйте close() для всех долгоживущих объектов перед выгрузкой плагина
  4. Принудительная сборка мусора - Вызовите collectgarbage() перед выгрузкой плагина для раннего запуска финализаторов
Исправление сбоя при выгрузке
-- Глобальные объекты, которые вызывали сбои
local g_config = nil
local g_cache = {}

function PluginStart()
    g_config = s2sdk.KeyValues.new("Config")
    g_cache["main"] = s2sdk.KeyValues.new("Main")
end

function PluginEnd()
    -- Очистить глобальные объекты
    if g_config then
        g_config:close()
        g_config = nil
    end

    -- Очистить кэшированные объекты
    for k, v in pairs(g_cache) do
        v:close()
    end
    g_cache = {}

    -- Опционально: Принудительный запуск GC для выполнения финализаторов
    collectgarbage()

    print("All resources cleaned up safely")
end

Совместимость версий Lua

Lua 5.4+

  • Полная поддержка переменных to-be-closed <close> (рекомендуется)
  • Финализатор __gc в качестве резерва
Lua 5.4+
-- Рекомендуется: Используйте <close>
local kv <close> = s2sdk.KeyValues.new("Config")
kv:SetName("ServerConfig")
-- Автоматически очищается при выходе из области видимости

Lua 5.1, 5.2, 5.3

  • Нет поддержки <close>
  • Только финализатор __gc
  • Рекомендуется явный close()
Lua 5.1-5.3
-- Рекомендуется: Явный close
local kv = s2sdk.KeyValues.new("Config")
kv:SetName("ServerConfig")
kv:close()  -- Явная очистка

-- Или полагайтесь на __gc (недетерминировано)
local kv2 = s2sdk.KeyValues.new("Config2")
-- Будет очищен в конечном итоге GC

См. также

  • Узнайте, как экспортировать свои собственные классы
  • Узнайте об импорте функций
  • Справочник по манифесту плагина