Перейти к контенту

Язык Lua. Общие вопросы программирования


Рекомендуемые сообщения

С чего начинать и где взять.

 

Установка Lua:
http://www.amk-team.ru/forum/index.php?showtopic=11584&p=629106

 

Руководство «Программирование на языке Lua», третье издание:
http://www.amk-team.ru/forum/index.php?showtopic=11584&p=905308

Изменено пользователем Kirgudu
Ссылка на комментарий

Gun12,

type(tonumber(time)) == 'number'

Хм. А это что за покемон? В чем сакральный смысл?

Я просто в восторге. Аж дух захватывает.

Это здорово. Что сам думаешь про такой подход вместо createTimer(xxx,xxx,xxx)?

На самом деле, тоже въехала в код, в который раз ЛУА меня удивил своими возможностями. Финты ушами такие можно делать, что мама не горюй. У тебя практически уже около ООП что-то: есть "описание класса" timer, и есть "конструктор" __call. И работаешь с инстансами. Круто. Чуть-чуть доработать и будет почти тот же функционал, что дает луабинд :)

Изменено пользователем xStream

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

Сакральный смысл?

Не хочу, чтобы сюда попали другие данные, кроме числа. Как такового, так и в виде строки. Для вычислений же не имеет значения. Конечно не абсолютная защита, но всё же. Об этом я подумаю позже :)

 

Добавлено через 33 мин.:

А насчет того, что я думаю по этому поводу - мыслей нет. Одни эмоции :)

Ссылка на комментарий
Не хочу, чтобы сюда попали другие данные, кроме числа.

А ты посмотри, что ты делаешь :) Сначала приводишь параметр к числу и проверяешь, число ли у тебя... Ну конечно число! Ты же только что приведение типа сделал :) Не нужен там tonumber, он тебе всю твою задумку ломает. Ибо type(tonгmber(x)) всегда 'number'

Двойка мне за знание матчасти. Все верно у тебя. Вот еще один нюанс луа с приведением типов обнаружился...

Изменено пользователем xStream

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

У меня сначала и было без tоnumbеr. А потом подумал что вдруг буду вычитывать из файла. И попадет строка.

Это только у меня сервер так тяжело идет?

 

Добавлено через 169 мин.:

xStream

Извини, перенесу нашу беседу в топик, т.к. назревает полезная информация.

Думаю что интересующимся понадобиться.

 

Я делаю для частного случая. Поэтому сам и создаю и прицепляю метатаблицы.

И вроде как незачем создавать подклассы.

На самом деле всё о чём ты говорошь не проблема. Есть стандартное описание создания подобного.

ИМХО, такое нужно применять для чего-то более масштабного чем таймеры в моей интерпритации :

function class() -- Конструктор класса
   cl = {}
   cl.__index = cl -- cl будет использоваться как мета-таблица
   return cl
end

function object( cl, obj ) -- Конструктор объекта
   obj = obj or {} -- возможно уже есть заполненные поля
   setmetatable(obj, cl)
   return obj
end

Some_Class = class() -- Создание класса Some_Class

function Some_Class:foo() print('Hi from foo') end -- Метод класса Some_Class

function Some_Class:new() -- Метод-Конструктор нового объекта класса Some_Class
   return object(self, { k = 12 })
end

obj = Some_Class:new() -- Созлание объекта класса

obj:foo()    --> Hi from foo
print(obj.k) --> 12

function subclass( pcl ) -- Конструктор дочернего класса
   cl = pcl:new() -- создаем экземпляр класса
   cl.__index = cl -- и делаем его классом
   return cl
end

Der_Class = subclass(Some_Class) -- Создание дочернего для Some_Class класса Der_Class

function Der_Class:new() -- Этот конструктор нужен для того, чтобы наследоваться от Der_Class и ...
   local obj = object(self, Some_Class:new())
   obj.yyy = 13 -- добавить новые поля в Der_Class а не в Some_Class
   return obj
end

function Der_Class:hello() print('Hi from hello') end -- Создание новых методов класса Der_Class

y = Der_Class:new() -- Создание объекта класса Der_Class

y:foo()   --> Hi from foo
y:hello() --> Hi from hello
print(y.k)   --> 12
print(y.yyy) --> 13

 

Изменено пользователем Gun12
Ссылка на комментарий

Это немного отличается от того, как сделано в луабинде. Там все намного более семантично сделано.

В твоем примере ты получаешь "класс", вызвав функцию class(). Это не является определением класса, имею ввиду, что это не общепринятый синтаксис, как во многих других языках. Стандартно пишется "ключевое слово", обозначающее, что сейчас будет определен класс, а потом - имя класса. Зачастую идет третий "параметр" - родительский(ие) класс(ы).

Суть в ЛУА не меняется. Эта информация так, для общего развития. Лично мне просто привычнее работать именно так, после С++, Java, Питона, ПХП. В Javascript используется прототипирование, там вообще все по-другому, как раз больше похоже на то, что ты описал.

Может оффтоп, а может информация для общего развития :)

ИМХО, такое нужно применять для чего-то более масштабного чем таймеры в моей интерпритации :

Лично мне такой способ объявления "дочерних классов" очень неудобен. Но это именно мое имхо и мои привычки. Но ООП на этом действительно можно спокойно реализовывать. И вообще, я где-то говорила, что это проблема? :) Луабинд все это реализовывает именно таким вот хитрым путем. Принцип его работы очень легко повторить.

Изменено пользователем xStream

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

xStream

Конечно это отличается от луабинда, т.к. это только всего лишь чистый lua :)

Эдак получится что нужно снова написать luabind. Так он уже есть...

Ссылка на комментарий

Попрошу не путать :) Луабинд ничего не добавляет к луа. Он реализует, образно говоря, некий функционал, используя только возможности самого луа. Если очень приспичит, я могу написать обертку, которая будет делать практически то же самое, что и луабинд - предоставлять возмжность создавать "классы" и реализовывать "наследование". Там всего-то две функции, а остальное игра с метатаблицами. Смысла только не вижу. Если хочется обсудить принцип реализации луабиндом этой фишки - прошу в личку. Еще раз - луабинд НИКАК не расширяет синтаксис ЛУА, а просто имеет "встроенный" дополнительный функционал. Вот что главное.

------------------------

Вывод всего этого - метатблицы и переопределение операторов - рулез.

 

Добавлено через 5 мин.:

А вообще, понятие "чистый ЛУА" смысла не имеет. Если вспомнить, что можно к интерпретатору подключать различные библиотеки, "базовая" комплектация содержит только самое основное. Никто не гнушается подключать библиотеки к интерпретатору. Потому мне кажется, что твое желание "изучить чистый ЛУА" не сильно логично. Лучше уж знать максимум, исопльзовать что нужно для своих целей исходя из задач.

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

xStream

Ты совершенно права. Постараюсь больше не путать.

Тем более что сторонними библиотеками совсем не гнушаюсь пользоваться.

 

Обсудить принцип реализации луабинда очень хочется. Только закончу таймеры.

Ссылка на комментарий

Если есть возможность, то попрошу вас обсуждать принцип реализации луабинд в этом топике, мне интересно почитать)

 

Добавлено через 73 мин.:

Вопрос, в том лаунчере функций(таймере), что я выкладывал, на апдейте висит "голый цикл"(ну или вот код функции апдейта лаунчера), это может повлиять на игру?, в смысле не будет ли лагов/фризов/подтормаживаний изза него? Информация в догонку: обычно в массиве self.aLauncher 1 постоянный таймер(сна, скоро добавится еще 1 постоянный таймер) и иногда разовые таймера запускаются...

function CSpLauncher:UpdateFuncs()
    if self.iTblSize < 1 then return end
    
    for sName, aFnc in pairs(self.aLauncher) do
        if aFnc.oTime:diffSec(game.get_game_time()) <= 0 then
            _log("CSpLauncher:UpdateFuncs:=Func:[%s] diffSec(%s):", sName, aFnc.oTime:diffSec(game.get_game_time()))
            local oFunction = loadstring(aFnc.sValue)
            self:DelFunc(sName)
            oFunction() --exemple    
        end
    end    
end

 

Изменено пользователем Viнt@rь
Ссылка на комментарий

Возможно более уместно было бы поместить материал в топик по скриптам, но исходный вариант был опубликован в этом, поэтому не стану распылять.

 

Вариант универсального хранилища 'xs_stor' свободного от некоторых ограничений.

 

Отличия от исходного варианта:

1. Собственно хранилище (массив storage) доступен с момента инициализации модуля (вместе с хелпером);

2. Хранилище заполняется по мере спавна в игру объектов хранения (custom_storage), что позволяет получать данные из хранилища еще до спавна актора.

Все ранее записанные данные доступны до момента подключения к серверу (см. в логе "Сервер: Соединяемся...");

3. Запись данных из хранилища осуществляется как и ранее по событию сохранения данных актора, что является единственной зависимостью модуля от сторонних событий;

4. Никаких внешних модулей работы с нет-пакетами не требуется. Объекты хранения (custom_storage) не метятся метками.

Чтение/запись данных производятся в штатных рабочих циклах объектов хранения (custom_storage) с одной копией доп.нет-пакета (stor-packet).

--[[---------------------------------------------------------------------
Библиотека универсального хранилища данных произвольного размера
Основана на использовани нетпакетов объектов (используется новый тип 
объектов, описанный в конфигах, имеющие произвольную длину пакета).
Хранилище подстраивается под размер хранимых данных.

Использовать можно только после загрузки игры (присутствует актор)
Удаление переменной производится так же, как и установка переменной - 
функцией set, как значение надо передать nil
Получение переменной производится функцией get, второй параметр - 
значение по-умолчанию (опциональный параметр). Если переменной не 
существует и не указано значение по-умолчанию, будет возвращен nil

Сразу после загрузки хранилища "кидается" событие storage_load
Непосредственно перед сохранением - storage_save

Разрешено хранить переменные следующих типов:
    булевое (хранится как байт)
    
    число (хранится всегда как float, если требуется хранение очень 
        больших чисел, то рекомендуется использовать хук приведения
        к строке: ""..1234567890 - передавать на вход set)
        
    строка (хранится как последовательность байт + нулевой символ, 
        конец строки; ограничение на длину строки - 8000 байт)
        
    таблицы (ПРОСТЫЕ! То есть: без метатаблиц, без нулевых символов и 
        прочих бинарных данных, без рекурсий и только с простыми типами 
        в ключах и значениях. Кроме того, объем таблицы очень сильно
        ограничен - при сериализации строка должна влазить в пакет, а 
        значит ограничение примерно в 8000 байт текста.)
    
Depends on:
    xs_sandbox
    xs_helpers (as sandbox`s module)
    --xs_netpk
--]]---------------------------------------------------------------------

-------------------------------------------------------------------------
--- xStream, 07/01/2012                                               ---
--- version 1.2                                                       ---
--- edited by Artos 09/01/2012
-------------------------------------------------------------------------


--private members

--local loaded = false
local storage = {}
local serialized_tables = {}
local allowed_types = { nil, true, 1, "", {} }    -- nil allowed for deletion
local allowed_types_rev = false
local max_packet_len = 8000
local new_elements_map = {}
local do_save = false --/ флаг сохранения в stor-объекты

--==============================================================

local function rebuild_allowed_types()
    local t, k = {}
    for k = 1, 5 do
        t[type(allowed_types[k])] = k
    end
    allowed_types_rev = t
end

--/ ---------------------------------------------
--/ чтение данных из stor-пакета в хранилище (from 'se_custom_storage')
--/ ---------------------------------------------
local function read_st_packet(pk)
    --/ read variables
    while not pk:r_eof() do --/ цикл (до конца)
        local lua_type = type(allowed_types[tail_pk:r_u8()])
        local key,val = pk:r_stringZ(), nil
        if lua_type then
            if lua_type == 'string' then
                val = pk:r_stringZ()
            elseif lua_type == 'number' then
                val = pk:r_float()
            elseif lua_type == 'boolean' then
                val = not (not pk:r_u8())
            elseif lua_type == 'table' then
                serialized_tables[key] = pk:r_stringZ()
                val = table.unserialize(serialized_tables[key])
            end
            storage[key] = val
        end
    end
end
--/ ---------------------------------------------
local function new_storage_element()
    local st_obj = alife():create("custom_storage", vector(), 0, 0)
    -- никогда не выйдет в онлайн
    st_obj:can_switch_online(false)
    st_obj:can_switch_offline(true)
    
    table.insert(new_elements_map, st_obj)
    return st_obj.st_packet --/> stor-packet !!!
end

local function load()
    event("storage_load"):trigger() --/#?# а нужен?
end

local function save()
    event("storage_save"):trigger() --/#?# а нужен?
    
    -- remove previous elements after previous save
    local _, sobj
    for _, sobj in ipairs(new_elements_map) do
        alife():release(sobj)
    end
    new_elements_map = {}
    
    if not allowed_types_rev then
        rebuild_allowed_types()
    end
    
    -- saving...
    do_save = true --/ начало записи
    local len = max_packet_len
    local lua_type, elm_len, tail_pk

    -- iterate variables
    for key, val in pairs(storage) do
        lua_type = type(val)
        if allowed_types_rev[lua_type] then
        
            -- calculate variable len for saving
            elm_len = 1 + string.len(key) + 1
            if lua_type=='string' then
                elm_len = elm_len + string.len(val) + 1
            elseif lua_type=='number' then
                elm_len = elm_len + 4
            elseif lua_type=='boolean' then
                elm_len = elm_len + 1
            elseif lua_type=='table' then
                elm_len = elm_len + string.len(serialized_tables[key]) + 1
            end
            
            if len + elm_len > max_packet_len then    -- element is full of data
                -- create new element to store variables
                -- ugly hook, but fastest solution :)
                tail_pk = new_storage_element()
                
                len = elm_len
            else    -- still have room to save
                len = len + elm_len
            end
            
            --write variable to tail
            tail_pk:w_u8(allowed_types_rev[lua_type])
            tail_pk:w_stringZ(key)
            if lua_type=='string' then
                tail_pk:w_stringZ(val)
            elseif lua_type=='number' then
                tail_pk:w_float(val)
            elseif lua_type=='boolean' then
                tail_pk:w_u8(val)
            elseif lua_type=='table' then
                tail_pk:w_stringZ(serialized_tables[key])
            end
            
        end
    end
    do_save = false --/ запись закончена
end

local function test()
    log1(">>>>>>>>>>"..tostring(get('test_2000', nil)))
    local k
    for k = 1, 2000 do
        set('test_'..k, 'long_test_string_value #'..k)
    end
end

local function test2()
    log1(">>>>>>>>>>dumping test table")
    
    print_table_inlog_v2(get('test_table', {}))
    
    local tbl = { 
        a = 1,
        b = {1,3,5},
        c = "abracadabra"
    }

    log1(">>>>>> set to stor: " .. tostring(set("test_table", tbl)))
end
--================================================================================

--public members
function init()
    event("actor_save"):register(save)
    event("actor_spawn"):register(load) --/#?# а нужен?
    --event("actor_spawn"):register(test)
    --event("actor_spawn"):register(test2)
    rebuild_allowed_types() --/ TODO: просто дать готовой таблицей!
end

-- in: string, any
-- out: any
function get(var_name, default)
    if type(storage[var_name]) == 'table' then
        return table.clone(storage[var_name])
    end
    return storage[var_name] or default
end

-- in: string, any
-- out: boolean(succes)
function set(var_name, value)
    local lua_type = type(value)
    
    if not allowed_types_rev then
    end
    
    if not allowed_types_rev[lua_type] then
        return false
    end
    local key = tostring(var_name)
    if lua_type=='table' then
    
        local serialized, err = table.serialize(value)
        if err or (string.len(serialized) + 1 > max_packet_len) then 
            return false
        end    
        
        serialized_tables[key] = serialized
        storage[key] = table.clone(value)
        return true
        
    elseif lua_type=='nil' then
        serialized_tables[key] = nil
    end
    
    storage[key] = value
    return true
end


-- класс специального серверного объекта для хранения данных
class "se_custom_storage" (cse_alife_dynamic_object)
function se_custom_storage:__init(section) super(section)
    self.st_packet = net_packet() --/ технологический stor-пакет для перезаписи данных (TODO: перепроверить для CS&SCoP)
    self.zero_pos = 0
end
function se_custom_storage:on_register()
    cse_alife_dynamic_object.on_register(self)
    --/#+# чтение данных из stor-пакета объекта в хранилище
    if not do_save and self.stor_pos > 0 then
        pk:r_seek(self.stor_pos) --/ читаем с 'stor_pos'
        read_st_packet(self.st_packet)
        alife():release(self,true) --/ clear
    end
end
function se_custom_storage:can_save() -- этот объект всегда будет сохраняться
    return true
end
-- при загрузке объекта его сохранённые данные будут прочитаны в специальный нетпакет
function se_custom_storage:STATE_Read(packet, size)
    cse_alife_dynamic_object.STATE_Read(self, packet, size)
    --/#+# перезапись данных из нет-пакета объекта в stor-пакет
    self:STATE_Copying(self.st_packet, packet, false)
end
-- при сохранении объекта в него будут скопированы данные из специального пакете
function se_custom_storage:STATE_Write(packet)
    cse_alife_dynamic_object.STATE_Write(self, packet)
    --/#+# перезапись данных из stor-пакета в нет-пакет объекта
    self:STATE_Copying(self.st_packet, packet, true)
end

--=============================thanx malandrinus for code below=============================================--
-- Из-за отсутствия в классе нетпакета метода w_seek приходится имитировать его действие.
-- Ограничение данного метода - устанавливает позицию записи не менее, чем в 2.
-- Также заполняет все пространство до указанной позиции каким-либо значением, 
-- в данном случае 123. В данном случае это неважно, поскольку эта область не используется.
--/ copying: перезапись (rewriting) данных из пакета в пакет
function se_custom_storage:STATE_Copying(reader, packet, is_save)
    if self.id == 65535 then return end --/> еще рано ... (подстраховка)
    if is_save then --/ сохранение из хранилища (reader: stor-пакет, packet: нет-пакет объекта)
        reader:r_seek(self.zero_pos) --/ установка позиции чтения stor-пакета в начало данных
    else --/ загрузка в хранилище (reader: нет-пакет объекта, packet: stor-пакет)
        --/ синхронизация: подготовка stor-пакета под запись данных
        self.zero_pos = reader:r_tell() --/ 'свободная для записи' позиция в нет-пакете объекта
        packet:w_begin()
        while packet:w_tell() ~= self.zero_pos do --/ установка позиции целевого пакета в 'zero_pos'
            packet:w_u8(123)
        end
    end
    --/ копирование данных из пакета в пакет
    while not reader:r_eof() do --/ цикл (до конца)
        packet:w_u8(reader:r_u8())
    end
end

 

P.S. Ссылка на архив со скриптом на народ.ру (~3.51 кБ)

 

P.P.S. Есть сомнения в работоспособности как исходного, так и данного варианта, в версиях игры ЧН/ЗП (CS/SCoP), из-за использования в процессе игры (промежуточные сэйвы) локальных копий нет-пакетов для буферных записей. Пока не проверял, если у кого будет информация - просьба сообщить (доработаем).

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Artos, будь дружественным пожалуйста :) - выложи файлом с нормальным форматированием...

 

ЗЫ:

Конечно если таковой есть...

Изменено пользователем 7.9

всё легко

Ссылка на комментарий

Gun12, Viнt@rь, вот для общего развития. Реализация "классов", подобная реализации луабинд. Можно "наследовать" классы, форма записи - такая же (определение классов).

Как видно из кода - реализация примитивнейшая средствами ЛУА, но результат...

_G.class = function (cls_name)
    if type(cls_name)~='string' then
        return function() end
    end
    
    local cls = {}
    local cls_mt = {}
    cls_mt.__call = function(...)
    
        local cls_obj = {
            parent = cls_mt.__index            -- imitate parent class, allows call parent's methods
        }
        local cls_obj_mt = { 
            __index = cls,
            __gc = function () 
                if cls_obj.__finalize then
                    cls_obj:__finalize()    -- imitate destructor
                end    
            end
        }
        
        setmetatable(cls_obj, cls_obj_mt)
        
        if cls_obj.__init then
            cls_obj:__init(...)                -- imitate constructor
        end
        
        return cls_obj
    end

    setmetatable(cls, cls_mt)
    
    _G[cls_name] = cls
    
    return function(parent_cls)
        cls_mt.__index = parent_cls
    end
end

--================================================

class "A"

function A:__init()
    self.bar = "A says yahoo"
    print("A:__init called")
end

function A:foo()
    print("A:foo called")
end

function A:__finalize()
    print("A instance is destroyed")
end

---------
class "A_der" (A)    -- this class is derived from A

function A_der:__init()
    self.parent:__init()    -- calls parent method
    print("A_der:__init called")
end

function A_der:lol()
    print("A_der:lol called")
end

---------
class "B"

function B:__finalize()
    print("B instance is destroyed")
end

function B:__init()
    self.bar = "B says boooo"
    print("B:__init called")
end

function B:foo()
    print("B:foo called")
end

---------
local a = A_der()
local b = B()
a:foo()        -- calls A:foo
a:lol()        -- calls A_der:lol
b:foo()        -- calls B:foo
print(tostring(a.bar))
print(tostring(b.bar))
a = nil
b = nil
collectgarbage()

 

Функционал даже чуть-чуть шире, чем у той версии луабинда, что использована в сталкере.

У каждого объекта класса есть свойство self.parent, указывающее на "родительский класс", если такой есть. Это полезно, когда и в родительском и в дочернем классе существуют методы с одинаковыми именами. Можно вызвать из дочернего метода родительский. Редко бывает нужно, но бывает. self.parent:__init(...) абсолютно идентично super(...) в сталкере - вызов конструктора родительского класса (опять же - если есть). Ну и еще одно отличие - классы помещаются в глобальную область видимости.

Изменено пользователем xStream

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

Наконец прорвало сервер.

Пока получились такие таймеры :

mtTimer = {}
timer   = {}

function timer:_init()
    self.__index    = self
    self.timers     = {}
    self.updateTime = 0
    setmetatable(self, mtTimer)
end

timer:_init()

function mtTimer:__call()
    local obj    = {}
    obj.complete = math.huge
    obj.remain   = 0
    obj.period   = 1  -- Значенние периода апдейта по умолчанию.
    setmetatable(obj, self)
    table.insert(timer.timers, obj)
    timer:_sort()
    return obj
end

function timer:_updateID(start_pos)
    start_pos = start_pos or 1
    local t   = self.timers
    for i = start_pos, #t do
        t[i].id = i
    end
end

function timer:_sort()
    table.sort(self.timers, function (a,b) return a.complete < b.complete end)
    self:_updateID()
end

-- Служебные функции -----------------------
function timer:Kill(n)
    local pos = n or self.id
    if self.name then _G[self.name] = nil end
    table.remove(self.timers, pos)
    self:_updateID(pos)
end

function timer:IsAction()      -- проверка, отработал ли таймер, т.е. наступило событие (boolean)
    return self.id == 1 and  os.time() >= self.complete
end

function timer:IsRunning()     -- проверка работает или остановлен таймер (boolean)
    return self.remain == nil
end

function timer:GetRemainTime() -- получение оставшегося времени работы таймера (number)
    local ostime  = os.time()
    if not self.complete then return 0 end
    return self.complete - ostime
end

function timer:GetTime()       -- получение установленного времени работы таймера (number)
    return self.time
end

function timer:GetAction()     -- получение имени функции-события (string)
    return self.action
end

function timer:GetName()       -- получение имени таймера (имя ключа таблицы _G) (string)
    return self.name
end

function timer:GetPeriod()     -- получение времени периода апдейта (number)
    return self.period
end
------------------------------------------------------------------------
function timer:save()          -- Сохранение всех созданных таймеров
    local file = io.open('C:\\save_test.ini','w')
    local tab  = self.timers
    local w    = {}
    for i = 1, #tab do
        local t = {}
        t[1] = tab[i].name     or '@'
        t[2] = tab[i].time     or '@'
        t[3] = tab[i].action   or '@'
        t[4] = tab[i].period   or '@'
        t[5] = tab[i].complete or '@'
        if tab[i].remain then t[6] = tab[i].remain end
        table.insert(w,table.concat(t,' '))
    end
    local s = table.concat(w,'|')
    file:write(s)
    file:close()
    return self
end

function timer:load()          -- Загрузка всех сохранённых таймеров
    timer.timers = {}
    local file  = io.open('C:\\save_test.ini','r')
    local s     = file:read()
    for w in s:gmatch('[^|]+') do
        local tab = {}
        for c in w:gmatch('%S+') do
            table.insert(tab,c)
        end
        local t = timer()

        if tab[1] ~= '@' then t:setName  (tab[1]) end
        if tab[2] ~= '@' then t:setTime  (tab[2]) end
        if tab[3] ~= '@' then t:setAction(tab[3]) end
        if tab[4] ~= '@' then t:setPeriod(tab[4]) end
        if tab[5] ~= '@' then
            local v = tonumber(tab[5])
            if not v then v = math.huge end
            t.complete = v
        end
        if tab[6] then t.remain   = tonumber(tab[6]) end
        
    end
    file:close()
    self:_sort()
    return self
end

function timer:stop()          -- Приостановка работы таймера
    local ostime  = os.time()
    if not self.complete then self.complete = ostime end
    self.remain   = self.complete - ostime
    self.complete = math.huge
    timer:_sort()
    return self
end

function timer:start()         -- Запуск остановленного таймера
    if not self.remain then self.remain = 0 end
    self.complete = os.time() + self.remain
    self.remain   = nil
    timer:_sort()
    return self
end

function timer:setPeriod(per)  -- Установка периода апдейта
    per = tonumber(per)
    if per then
        local rem = self:GetRemainTime()
        if per >= rem then per = rem end -- если период задан больше оставшегося времени работы,
                                         -- то установить его равным остатку времени работы
        self.period = per
    end
    return self
end

function timer:setName(nm)     -- Установка имени таймера (поля с указанным именем в таблице _G)
    nm = type(nm) == 'string' and nm
    if nm then
        if _G[self.name] then _G[self.name] = nil end
        self.name = nm
        _G[nm] = self
    end
    return self
end

function timer:setAction(act)  -- Установка имени функции-события по завершению работы таймера
    act = type(act) == 'string' and type (_G[act]) == 'function' and act
    if act then
        self.action = act
    end
    return self
end

function timer:setTime(tm)     -- Установка времени работы таймера
    tm = tonumber(tm)
    if tm then
        self.time     = tm
        self.complete = os.time() + (tm or -1)
        self.remain   = nil
        self:_sort()
    end
    return self
end

-- Апдейт
function timer:update()        -- На апдейт
    local ostime = os.time()
    if ostime >= self.updateTime then
        local t = self.timers[1]
        if t and not t.remain then
            local r = t.complete - ostime
            if r < t.period then t.period = r end
            self.updateTime = ostime + (t.period or 1)
            if r <= 0 then
                if t.action then
                    _G[t.action]()
                end
                self:Kill(1)
                self:update()
            end
        end
    end
end
----------------------------------------------
-- При создании таймера настоятельно рекомендую не сохранять объект таймера в переменную.
-- Используйте метод setName('имя_таймера').
-- Переменная с этим именем будет создана в глобальной таблице автоматически :
-- Не timer_1 = timer()
-- А  timer():setName('timer_1')
-- И затем уже обращаться к ней так же, как к объявленной стандартным способом :
-- timer_1:setTime(12)
----------------------------------------------
-- Создание таймера :
-- timer():setPeriod(2):setAction('Action1'):stop():setTime(2):setName('TIMER'):start() и т.д.
-- Служебные функции вызываются :
-- if TIMER:IsRunning() then

 

Ссылка на комментарий

Перечитав тему, примерно так представляются требования для универсального хранилища:

1. У каждого bindable объекта есть метод save(…). Метод знает как проверять и сохранять данные в зависимости от типа (number, table, etc…).

2. Данные сохраняются в специально созданный объект хранилище. Pstor не используется (оставим для оригинального функционала)

3. По мере надобности (лимит 8К?) создаются дополнительный(е) хранилища (transparent to end user) и присоединяются к родительскому объекту наравне с уже созданным

4. Объект хранилище содержит информацию про тот объект которому принадлежит (parent object)

5. Данные можно получить методом obj:read() который пройдет по всем созданным хранилищам данного объекта и считает нужную информацию.

6. Есть некий универсальный (ни кому не принадлежащий) объект с методами save() and read() который можно использовать в случаях когда нет логического родителя – примерно то что сделала xStream, как я понял.

 

Если что не так выразил поправьте и дополните - пытаюсь разобраться с хранилищами

 

П.С. Сервер глючит не по детски

 

 

 

Изменено пользователем Andrey07071977
Ссылка на комментарий

Gun12

Что-то как-то громоздко. И я не понимаю интерфейса (имхо, тоже перегружен - мне кажется, геттеры и сеттеры в чистом виде не нужны. В "методы" обернуть бы более комплексную логику). Зачем ограничение, что таймер нельзя сохранять в переменной? Это не удобно, всегда присваивать имя. Это аналогично безымянным функциям - они есть, работают, можно передавать, но имени нет. ИМХО.

Изменено пользователем xStream

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

Доработанный вариант универсального хранилища 'xs_stor' свободного от некоторых ограничений.

1. Упрощен/оптимизирован процесс записи/чтения нет-пакетов объектов хранения. Никаких лишних операций.

2. Операция по сериализации хранимых в хранилище таблиц оставлена только непосредственно при сохранении таблий в объекты хранения., что дало возможность оптимизировать оперции чтеия/записи в собственно хранилище.

 

Ссылка на народ.ру (~6.97 кБ).

В архиве два скрипта, второй (se_stor) - проверенный в работе вариант от Симбиона с некоторыми нюансами (дан для сравнения).

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Предлагаю взглянуть на мою реализацию системы событий, хранилища и таймеров, которую я делал в рамках OGSE. Реализация независимая, возможно кому-то покажется интересной.

http://rghost.net/35846466

Здесь привожу только описание использования, детали смотри внутри.

 

Система событий

Названа по аналогии с boost "сигналы и слоты". Подписчики - это слоты, их соответственно дёргают сигналы. Подписывать можно функции, функциональные объекты и методы объектов. Слот можно подписать для вызова как обычный (или высокоприоритетный) и очерёдный (низкоприоритетный) вызов. Обычные вызываются все разом при срабатывании сигнала, очерёдные - по очереди, по одному на срабатывание сигнала. Всё это используется в таймерах. Имеется сервис автоподключения модулей.

Общие принципы

Вся работа с системой происходит через глобальный объект менеджера сигналов. Получаем его функцией

local sm = ogse_signals.get_mgr()

Для подписывания есть метод менеджера

sm:subscribe(slot_desc)

для отписывания

sm:unsubscribe(slot_desc)

slot_desc - это описание слота, дескриптор слота. Представляет собой таблицу

slot_desc = {signal = "signal_name", self = obj, fun = function, queued = true}

идею вы уже надеюсь поняли.

signal - имя сигнала, по нему потом будем вызывать.

fun - глобальная функция, метод класса или функциональный объект

self - объект класса, если fun - это метод

queued - если true, вызов будет вызываться с низким приоритетом. Это можно сделать для любого сигнала, но очевидно в основном имеет смысл для периодических проверок по апдейтам.

Для отписывания надо передать в точности тот же набор данных, поэтому он и объединён в таблицу. Имеет смысл эту таблицу сохранить, если потом надо отписать вызов. Соответственно, я предпочитаю называть такую таблицу дескриптором слота.

 

Вызов сигнала происходит следующим образом:

local sm = ogse_signals.get_mgr()

sm:call("signal_name", arg1, arg2,...)

Я предпочитаю передавать аргументы так, как показано выше, по старинке списком. С другой стороны ничто не мешает передать их таблицей в стиле

sm:call("signal_name", {arg1 = valu1, arg2 = value2})

цепочку вызовов можно прервать. Для этого вызов должен вернуть true. Возвращаемые значения false, nil или ничего просто игнорируются.

 

Примеры

 

Подписывание глобальной функции

function some_global_function(arg1, arg2)
end
local slot_desc = {signal = "signal_name", fun = some_global_function, queued = true}
ogse_signals.get_mgr():subscribe(slot_desc) -- подписали в низкоприоритетную очередь
--...
--вызвали сигнал
local sm = ogse_signals.get_mgr()
--...
sm.call("signal_name", arg1, arg2)
--...
ogse_signals.get_mgr():unsubscribe(slot_desc) -- отписали

 

Подписывание произвольного метода класса

автор идеи для примера Andrey07071977:

Как быть в ситуациях когда в классе несколько методов которые необходимо подписать на разные события? Например, есть класс "actor_hit_effect", в нем два метода-подписчика - один на actor_hit, другой на item_use.

local hit_effect = actor_hit_effect()
sm:subscribe({signal = "on_actor_hit", self = hit_effect, fun = hit_effect.on_hit})
sm:subscribe({signal = "on_item_use", self = hit_effect, fun = hit_effect.on_use})

или даже так. В коде ниже класс подписывает свои же методы в своём конструкторе. В методе unsubscribe_itself объект класса может отписать методы от событий, используя запомненные ранее дескрипторы.

class "actor_hit_effect"
function actor_hit_effect:__init()
    self.sm = ogse_signals.get_mgr()
    self.desc_on_hit = {signal = "on_actor_hit", self = self, fun = self.on_hit}
    self.sm:subscribe(self.desc_on_hit)
    self.desc_on_use = {signal = "on_item_use", self = self, fun = self.on_use}
    self.sm:subscribe(self.desc_on_use)
end
function actor_hit_effect:unsubscribe_itself()
    self.sm:unsubscribe(self.desc_on_hit)
    self.sm:unsubscribe(self.desc_on_use)
end

 

class "some_luabind_class"
function some_luabind_class:__init()
    local mt = getmetatable(self)
    mt.__call = self.method_to_call
end
function some_luabind_class:method_to_call(arg1, arg2)
end

local slot_desc = {signal = "signal_name", fun = some_luabind_class()}
ogse_signals.get_mgr():subscribe(slot_desc) -- подписали в высокоприоритетную очередь
-- вызвали
sm.call("signal_name", arg1, arg2)
-- отписали
ogse_signals.get_mgr():unsubscribe(slot_desc)

--Функциональный класс на таблице строится так
local t = {}
function t:method_to_call()
end
local mt = {}
mt.__call = t.method_to_call
getmetatable(t, mt)
end
-- подписывается и вызывается совершенно аналогично
ogse_signals.get_mgr():subscribe({signal = "signal_name", fun = t})

 

Примечание: вариант с функциональным классом - это в сущности редко нужные изыски. С точки зрения Lua функциональный класс - это та же функция. Поскольку можно подписать функцию, то можно и функциональный класс.

 

Для подключения модуля его надо прописать в таблицу ogse_signals.addons. В самом модуле должна иметься функция attach, в которую будет передана ссылка на объект менеджера сигналов. Для автоподключения модуля в нём должна быть переменная auto_attach = true, а имя модуля должно начинаться с "ogse_". Впрочем, это фишка нерекомендуемая для использования в релизе, скорее для отладочных и тестовых модулей, которые надо подключать без вмешательства в существующий код. У автоподключения есть также одна особенность. Если в модуле имеется синтаксическая ошибка, то он будет просто проигнорирован при автоподключении (поскольку в этом случае никак не определить, что у него внутри и есть ли там функция attach и переменная auto_attach). Если же модуль прописан в таблице и в нём ошибка, то тогда система ругнётся.

 

 

Система хранения

Система хранения имеет два уровня. На первом имеются специальные объекты хранения, которые можно использовать и сами по себе, но при этом традиционно ограниченные размером одного нетпакета. Объекты хранения предоставляют базовый сервис создания таких объектов, их регистрации, примитивной записи и чтения данных. Здесь подробнее о них не рассказываю, подробности смотри внутри.

На основе этих объектов уже строится высокоуровневая система с произвольным объёмом. Из особенностей отмечу только достаточно подробную типизацию, с возможностью записывать целые числа разного размера, числа float, строки, логические значения, вектора, объекты CTime, массивы и куски бинарных данных. Ограничение на один элемент - не больше нетпакета. Преодолевать его не пытался и думаю, что и не буду пытаться, поскольку не вижу в этом ни малейшей необходимости.

 

ogse.save_variable(name, value [,type]) -- сохранить переменную

ogse.load_variable(name [,default_value [,type]]) -- прочитать переменную

ogse.variable_exists(name) -- проверить существование переменной

ogse.delete_variable(name) -- удалить переменную, чтобы освободить место

name - строка с уникальным именем переменной

value - значение

type - строка с типом. Это в ряде случаев необязательный параметр.

Возможные варианты:

"string" - строка, занимает количество символов + 1

"bool" - логическое значение, 1 байт

"float" - яисло с плавающей запятой, 4 байта

"vector" - вектор из 3-х float, 12 байт

"time" - время CTime, 8 байт

"u8", "s8", "u16", "s16", "u32", "s32" - знаковые и беззнаковые целые, 1,2,4 байта соответственно

"number" - автораспознаваемый тип числа, хранится в строковом виде. длина зависит от значения. Этот тип не нужно указывать, он будет назначен переменной автоматически, если значение имеет тип числа.

"chunk" - представляет собой нетпакет, или попросту кусок двоичных данных произвольной длины. Это планировалось как основа для создания отдельных хранилищ, совместимых с текущей идеологией. Т.е. везде, где для сохранения/загрузки передаётся нетпакет, его можно заменить на наш отдельный, а его уже хранить в этом общем хранилище. Смысл этого в экономии места при организации сохранения разных подсистем. Для каждой подсистемы или

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

"array" - тип массива. Значение - таблица вида {v1, v2, v3}, где тип значений массива ограничен автораспознаваемыми типами: строки, логические значения и числа.

 

параметр type не является обязательным в случае типа значения строка, логическое значение и число. В случае числа при неуказании точного типа числа оно просто сохраняется в строковом виде. Поэтому желательно его указывать. Для чисел с плавающей запятой это существенно уменьшит размер хранимых данных, так как при указании типа "float" число будет сохранено в двоичном виде как 4 байта, строка же может занять до 16-и байт. Во всех остальных случаях, вектор, объект CTime, chunk, array, параметр надо указывать обязательно, так как в этом случае нет возможности тривиально определить природу вргумента. Если в этом случае не указать тип, то будет вылет с соответствующим сообщением.

 

при чтении переменной минимально достаточно указать её имя

local value = ogse.load_variable(name)

Однако, переменная должна существовать. Если такой ранее не было сохранено, то будет вылет (намеренно). Чтобы этого избежать, надо указывать значение по умолчанию.

local value = ogse.load_variable(name, default_value [,type])

тип, как необязательный параметр, указывается по тем же правилам, что и при сохранении.

 

Таймеры

Таймеры имеются двух типов "быстрые" и сохраняемые (сериализуемые). Работают те и другие в целом по одному принципу. Создаётся объект таймера, который имеет в общем случае метод условия и действия. Условие периодически проверяется, когда возвращает true, то таймер останавливается и вызывется метод действия. Для срабатывания в определённый момент времени или через определённый промежуток есть упрощённый сервис, с которым достаточно переопределить только метод действия. Отличие сохраняемых в ряде мелких деталей, и наличии методов save/load, которые в общем работают как методы биндера или серверного класса и позволяют сохранить пользовательские данные таймера в нетпакете.

Применение "быстрого" таймера сводится к написанию простого класса, в общем случае содержащего: конструктор, условие и действие, созданию экземплара такого класса и вызову его метода start.

Пример 1. Простейший таймер, делающий что-то через заданное время после запуска.

class "simple_timer" (ogse_qt.quick_timer)
function simple_timer:__init(time) super(time)
end
function simple_timer:taction() -- наше действие по таймауту
    -- здесь что-то делаем по факту срабатывания таймера
end
--Вызов таймера
simple_timer(3000):start() -- сработает через три секунды
simple_timer(10000):start(true) -- сработает через 10 секунд, и проверяться будет реже

Пример 2 использования таймера для отслеживания удаления объекта из инвентаря:

class "release_watch_timer" (ogse_qt.quick_timer)
function release_watch_timer:__init(id) -- здесь решили не использовать таймаут
    self.client_id = id -- запомнили объект, за которым следим
end
function release_watch_timer:condition()
    return not level.object_by_id(self.client_id) -- сработает по исчезновению клиентского объекта
end
function release_watch_timer:action() -- наше действие
    -- здесь что-то делаем по факту ухода объекта в оффлайн
end
--Использование:
alife():release(sobj, true)
release_watch_timer(sobj.id):start()

Примечания к примерам:

1. Наличие вашего конструктора обязательно. Вызов конструктора базового класса super(timeout) нужен только если используется возможность завершения по таймауту. Возможность задания таймаута - это просто способ задать выполнение действия через заданный промежуток времени без написания специального условия. При использовании этой возможности можно ограничиться только заданием действия.

2. При останове по таймауту действие, заданное методом action, выполнено не будет. Вместо него выполнится действие, заданное taction.

3. Пока таймер работает, можно остановить его работу принудительно так

timer_object:stop()

В этом случае никакое действие выполнено не будет.

 

Пример создания и использования простого таймера, использующего только временнОе условие.

class "test_timer" (ogse_st_mgr.savable_timer)
function test_timer:__init() super()
    -- обязательная строка с именем класса таймера
    self._class = script_name()..".test_timer"
end
-- метод для первичной инициализации, может иметь произвольное имя
function test_timer:set_data(s)
    self.str = s
end
-- перегруженные методы загрузки/сохранения пользовательских данных
function test_timer:load(packet)
    self.str = packet:r_stringZ()
end
function test_timer:save(packet)
    packet:w_stringZ(self.str)
end
-- перегруженный метод действия по временнОму условию
function test_timer:taction()
    log1(self.str) -- что-то делаем в назначенное время
end
-- создание и запуск таймера
local t = test_timer()
t:set_data("some string")
t:set_delay(600) -- сработает через 10 минут реального времени
-- или
--t:set_gdelay(600) -- сработает через 10 минут игрового времени
-- или
--t:set_gtime(game.CTime():set(...)) -- сработает в заданный момент игрового времени
t:start() -- запустить с низким приоритетом (по умолчанию)
-- или
--t:start(true) -- запустить с высоким приоритетом (проверка на каждом апдейте)

Пример таймера с произвольным условием. Следит за неким неписем, который когда-то может быть выйдет в онлайн.

class "conditional_timer" (ogse_st_mgr.savable_timer)
function conditional_timer:__init(timer_id) super(timer_id)
    self._class = script_name()..".conditional_timer"
end
function conditional_timer:init(npc_id)
    self.npc_id = npc_id
    return self -- для вызова цепочкой
end
-- перегруженные методы загрузки/сохранения пользовательских данных
function conditional_timer:load(packet)
    self.npc_id = packet:r_u16()
end
function conditional_timer:save(packet)
    packet:w_u16(self.npc_id)
end
function conditional_timer:condition()
    self.npc = level.object_by_id(self.npc_id)
    return self.npc
end
-- перегруженный метод действия по пользовательскому условию (имя метода action, а не taction !)
function conditional_timer:action()
    self.npc:kill(db.actor) -- что-то делаем при появлении чувака
end
-- Создание таймера, инициализация и запуск с низким приоритетом
conditional_timer("my_timer"):init(12345):start()
ogse_st_mgr.get_timer("my_timer"):stop() -- передумали и остановили

Примечания:

1. При написании конструктора единственным обязательным элементом является инициализация служебного поля self._class. Туда надо записать строку с полным путём до конструктора класса. Если вы находитесь в модуле с расширением *.script, то может помочь функция script_name(), которая вернёт имя модуля, но имя класса придётся дописать самостоятельно. К сожалению, у Lua функции нет тривиального способа узнать своё собственное имя. В итоге эта строка всегда будет выглядеть примерно так:

self._class = script_name()..".test_timer"

или так:

self._class = "some_module.test_timer"

если вы решили написать имя файла явно.

2. Конструктор может иметь только один аргумент или ни одного. Если аргумент есть, то это имя таймера, по которому его можно потом получить для каких-либо манипуляций. Таймер можно остановить или изменить его условия. Если этого не планируется делать, то имя задавать не обязательно. Если имя не задано, то при конструировании таймеру будет автоматически назначено некое уникальное имя на основе первого доступного целого числа.

3. Как и в случае с "быстрыми" таймерами у сохраняемых таймеров имеется сервис срабатывания по временнОму условию без необходимости писать метод с пользовательским условием. Для задания времени останова надо после создания таймера вызвать один из следующих методов:

timer_object:set_delay(real_time_delay) -- аргумент - задержка срабатывания от текущего момента в секундах реального времени (может быть дробное число)

timer_object:set_gdelay(game_time_delay) -- аргумент - задержка срабатывания от текущего момента в секундах игрового времени (может быть дробное число)

timer_object:set_gtime(game.CTime():set(...)) -- аргумент - точное время срабатывания в терминах игрового времени. Объект CTime

4. Как и для "быстрых" таймеров можно регулировать приоритетность проверок аргументом метода start. Однако, в отличие от "быстрых" таймеров, значение по умолчанию (оно же false) будет означать проверки с низким приоритетом. Это более часто встречающийся случай для сохраняемых таймеров, которые работают долго, а точность срабатывания до секунды вполне удовлетворительна.

5. Дополнительные данные для таймера, которые будут в нём храниться и которые он может использовать как для проверки пользовательского условия так и для выполнения действия надо задавать при его создании специальным методом, который надо добавить к классу таймера и использовать в момент между созданием и запуском. Этот метод может иметь произвольное имя. В дальнейшем пользовательские данные будут загружаться в таймер при загрузке игры методом load, и сохраняться в методе save. Метод инициализации кроме как в первый раз при создании использовать нельзя. Сохранение и загрузка данных осуществляются в нетпакете, переданном как аргумент методов save/load. Тут всё работает примерно также, как и при сохранении данных в биндере или серверном объекте. В общем-то, никто не мешает использовать и другие техники сохранения и игнорировать переданный нетпакет.

6. Время срабатывания, установленное методами set_delay, set_gdelay и set_gtime, сохраняется само и никаких дополнительных манипуляций не требует.

 

Изменено пользователем malandrinus
 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Ссылка на комментарий

Вчера так и не смог толком пробиться на сайт. Ошибка 504 и всё тут. Отправлял сообщение наобум. Получился даблпост. Сорри.

xStream

По поводу луабинда. Конечно шикарно. Высший пилотаж. Код профессионала заметно отличается от любительского.

 

По поводу сетНэйм.

Если бы не нужно было сохранять и загружать то я бы и не придумал её.

И если бы имена таймеров задавались только глобально, то парсил бы _G (ну или _М) и получал имя переменной. А если локально? Где брать имя для загрузки? Например :

-- В какой-то функции на апдейте
local t
-- условие, которое выполняется один раз
if условие then
t = timer():setTime(x)
end
if t and t:action() then
...действие
end

При загрузке получится что таймер то есть, но 't' будет nil. Как получить имя переменной в таком случае ума не приложу. Вот и не стал мучаться. Лучше других помучать :)

Ссылка на комментарий
Gun12, да никакого высшего пилотажа там нет :) Только синтаксис ЛУА. Класс - таблица-прототип, хранящая логику. Объект класса - тоже таблица, но хранящая данные. По метаметоду __index ссылается на таблицу-прототип. Наследование реализуется тоже с помощью __index - таблица-прототип ссылается на другу (другой класс) получая оттуда другую логику. Элементарно.

Все, кто стоит на моем пути: идите нахрен и там погибните! ©

Ссылка на комментарий

Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти
  • Недавно просматривали   0 пользователей

    • Ни один зарегистрированный пользователь не просматривает эту страницу.
×
×
  • Создать...