Dennis_Chikin 3 658 Опубликовано 4 Января 2015 Поделиться Опубликовано 4 Января 2015 (изменено) С чего начинать и где взять. Установка 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 Изменено 2 Марта 2015 пользователем Kirgudu Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
xStream 86 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 (изменено) Gun12, type(tonumber(time)) == 'number' Хм. А это что за покемон? В чем сакральный смысл? Я просто в восторге. Аж дух захватывает. Это здорово. Что сам думаешь про такой подход вместо createTimer(xxx,xxx,xxx)? На самом деле, тоже въехала в код, в который раз ЛУА меня удивил своими возможностями. Финты ушами такие можно делать, что мама не горюй. У тебя практически уже около ООП что-то: есть "описание класса" timer, и есть "конструктор" __call. И работаешь с инстансами. Круто. Чуть-чуть доработать и будет почти тот же функционал, что дает луабинд Изменено 9 Января 2012 пользователем xStream Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Nazgool 250 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 Сакральный смысл? Не хочу, чтобы сюда попали другие данные, кроме числа. Как такового, так и в виде строки. Для вычислений же не имеет значения. Конечно не абсолютная защита, но всё же. Об этом я подумаю позже Добавлено через 33 мин.: А насчет того, что я думаю по этому поводу - мыслей нет. Одни эмоции Ссылка на комментарий
xStream 86 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 (изменено) Не хочу, чтобы сюда попали другие данные, кроме числа. А ты посмотри, что ты делаешь Сначала приводишь параметр к числу и проверяешь, число ли у тебя... Ну конечно число! Ты же только что приведение типа сделал Не нужен там tonumber, он тебе всю твою задумку ломает. Ибо type(tonгmber(x)) всегда 'number' Двойка мне за знание матчасти. Все верно у тебя. Вот еще один нюанс луа с приведением типов обнаружился... Изменено 9 Января 2012 пользователем xStream Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Nazgool 250 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 (изменено) У меня сначала и было без 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 Изменено 9 Января 2012 пользователем Gun12 Ссылка на комментарий
xStream 86 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 (изменено) Это немного отличается от того, как сделано в луабинде. Там все намного более семантично сделано. В твоем примере ты получаешь "класс", вызвав функцию class(). Это не является определением класса, имею ввиду, что это не общепринятый синтаксис, как во многих других языках. Стандартно пишется "ключевое слово", обозначающее, что сейчас будет определен класс, а потом - имя класса. Зачастую идет третий "параметр" - родительский(ие) класс(ы). Суть в ЛУА не меняется. Эта информация так, для общего развития. Лично мне просто привычнее работать именно так, после С++, Java, Питона, ПХП. В Javascript используется прототипирование, там вообще все по-другому, как раз больше похоже на то, что ты описал. Может оффтоп, а может информация для общего развития ИМХО, такое нужно применять для чего-то более масштабного чем таймеры в моей интерпритации : Лично мне такой способ объявления "дочерних классов" очень неудобен. Но это именно мое имхо и мои привычки. Но ООП на этом действительно можно спокойно реализовывать. И вообще, я где-то говорила, что это проблема? Луабинд все это реализовывает именно таким вот хитрым путем. Принцип его работы очень легко повторить. Изменено 9 Января 2012 пользователем xStream Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Nazgool 250 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 xStream Конечно это отличается от луабинда, т.к. это только всего лишь чистый lua Эдак получится что нужно снова написать luabind. Так он уже есть... Ссылка на комментарий
xStream 86 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 Попрошу не путать Луабинд ничего не добавляет к луа. Он реализует, образно говоря, некий функционал, используя только возможности самого луа. Если очень приспичит, я могу написать обертку, которая будет делать практически то же самое, что и луабинд - предоставлять возмжность создавать "классы" и реализовывать "наследование". Там всего-то две функции, а остальное игра с метатаблицами. Смысла только не вижу. Если хочется обсудить принцип реализации луабиндом этой фишки - прошу в личку. Еще раз - луабинд НИКАК не расширяет синтаксис ЛУА, а просто имеет "встроенный" дополнительный функционал. Вот что главное. ------------------------ Вывод всего этого - метатблицы и переопределение операторов - рулез. Добавлено через 5 мин.: А вообще, понятие "чистый ЛУА" смысла не имеет. Если вспомнить, что можно к интерпретатору подключать различные библиотеки, "базовая" комплектация содержит только самое основное. Никто не гнушается подключать библиотеки к интерпретатору. Потому мне кажется, что твое желание "изучить чистый ЛУА" не сильно логично. Лучше уж знать максимум, исопльзовать что нужно для своих целей исходя из задач. Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Nazgool 250 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 xStream Ты совершенно права. Постараюсь больше не путать. Тем более что сторонними библиотеками совсем не гнушаюсь пользоваться. Обсудить принцип реализации луабинда очень хочется. Только закончу таймеры. Ссылка на комментарий
Viнt@rь 50 Опубликовано 9 Января 2012 Поделиться Опубликовано 9 Января 2012 (изменено) Если есть возможность, то попрошу вас обсуждать принцип реализации луабинд в этом топике, мне интересно почитать) Добавлено через 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 Изменено 9 Января 2012 пользователем Viнt@rь GUI для конвертера от бардака(всего и вся в форматы сдк) Полезный утиль-"Utilits pack(mod)" Ссылка на комментарий
Artos 99 Опубликовано 10 Января 2012 Поделиться Опубликовано 10 Января 2012 (изменено) Возможно более уместно было бы поместить материал в топик по скриптам, но исходный вариант был опубликован в этом, поэтому не стану распылять. Вариант универсального хранилища '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), из-за использования в процессе игры (промежуточные сэйвы) локальных копий нет-пакетов для буферных записей. Пока не проверял, если у кого будет информация - просьба сообщить (доработаем). Изменено 10 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
7.9 174 Опубликовано 10 Января 2012 Поделиться Опубликовано 10 Января 2012 (изменено) Artos, будь дружественным пожалуйста - выложи файлом с нормальным форматированием... ЗЫ: Конечно если таковой есть... Изменено 10 Января 2012 пользователем 7.9 всё легко Ссылка на комментарий
Andrey07071977 18 Опубликовано 10 Января 2012 Поделиться Опубликовано 10 Января 2012 (изменено) 7.9 держи Изменено 10 Января 2012 пользователем Andrey07071977 Ссылка на комментарий
xStream 86 Опубликовано 10 Января 2012 Поделиться Опубликовано 10 Января 2012 (изменено) 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(...) в сталкере - вызов конструктора родительского класса (опять же - если есть). Ну и еще одно отличие - классы помещаются в глобальную область видимости. Изменено 10 Января 2012 пользователем xStream Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Nazgool 250 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 Наконец прорвало сервер. Пока получились такие таймеры : 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 Ссылка на комментарий
Andrey07071977 18 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 (изменено) Перечитав тему, примерно так представляются требования для универсального хранилища: 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, как я понял. Если что не так выразил поправьте и дополните - пытаюсь разобраться с хранилищами П.С. Сервер глючит не по детски Изменено 11 Января 2012 пользователем Andrey07071977 Ссылка на комментарий
xStream 86 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 (изменено) Gun12 Что-то как-то громоздко. И я не понимаю интерфейса (имхо, тоже перегружен - мне кажется, геттеры и сеттеры в чистом виде не нужны. В "методы" обернуть бы более комплексную логику). Зачем ограничение, что таймер нельзя сохранять в переменной? Это не удобно, всегда присваивать имя. Это аналогично безымянным функциям - они есть, работают, можно передавать, но имени нет. ИМХО. Изменено 11 Января 2012 пользователем xStream Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Artos 99 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 Доработанный вариант универсального хранилища 'xs_stor' свободного от некоторых ограничений. 1. Упрощен/оптимизирован процесс записи/чтения нет-пакетов объектов хранения. Никаких лишних операций. 2. Операция по сериализации хранимых в хранилище таблиц оставлена только непосредственно при сохранении таблий в объекты хранения., что дало возможность оптимизировать оперции чтеия/записи в собственно хранилище. Ссылка на народ.ру (~6.97 кБ). В архиве два скрипта, второй (se_stor) - проверенный в работе вариант от Симбиона с некоторыми нюансами (дан для сравнения). "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Ссылка на комментарий
Malandrinus 615 Опубликовано 11 Января 2012 Автор Поделиться Опубликовано 11 Января 2012 (изменено) Предлагаю взглянуть на мою реализацию системы событий, хранилища и таймеров, которую я делал в рамках 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, сохраняется само и никаких дополнительных манипуляций не требует. Изменено 11 Января 2012 пользователем malandrinus Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
Nazgool 250 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 Вчера так и не смог толком пробиться на сайт. Ошибка 504 и всё тут. Отправлял сообщение наобум. Получился даблпост. Сорри. xStream По поводу луабинда. Конечно шикарно. Высший пилотаж. Код профессионала заметно отличается от любительского. По поводу сетНэйм. Если бы не нужно было сохранять и загружать то я бы и не придумал её. И если бы имена таймеров задавались только глобально, то парсил бы _G (ну или _М) и получал имя переменной. А если локально? Где брать имя для загрузки? Например : -- В какой-то функции на апдейте local t -- условие, которое выполняется один раз if условие then t = timer():setTime(x) end if t and t:action() then ...действие end При загрузке получится что таймер то есть, но 't' будет nil. Как получить имя переменной в таком случае ума не приложу. Вот и не стал мучаться. Лучше других помучать Ссылка на комментарий
xStream 86 Опубликовано 11 Января 2012 Поделиться Опубликовано 11 Января 2012 Gun12, да никакого высшего пилотажа там нет Только синтаксис ЛУА. Класс - таблица-прототип, хранящая логику. Объект класса - тоже таблица, но хранящая данные. По метаметоду __index ссылается на таблицу-прототип. Наследование реализуется тоже с помощью __index - таблица-прототип ссылается на другу (другой класс) получая оттуда другую логику. Элементарно. Все, кто стоит на моем пути: идите нахрен и там погибните! © Ссылка на комментарий
Рекомендуемые сообщения
Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий
Комментарии могут оставлять только зарегистрированные пользователи
Создать аккаунт
Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!
Зарегистрировать новый аккаунтВойти
Есть аккаунт? Войти.
Войти