Artos 99 Опубликовано 8 Января 2012 (изменено) Viнt@rь, вот тут ты заблуждаешься. То, что появляется в биндере - до загрузки нет-пакета не тот самый актор,а клиентская копия с серверного шаблона актора (заготовка). Только когда серверная копия прочитает весь нет-пакет актора - появится в игре полный серверный объект актора а не его куций абстракт ... Только когда забинденый (созданный биндером шаблон) клиентский объект актора считает свой pstor - в игре появится полноценный актор. Можно конечно сохранять данные не в pstor'е, а в custom-data актора - тогда можно ускорить доступ к данным, но ... Добавлено через 5 мин.: ты даже не берешь Ид актора функцией, а сам назначаешь ему Ид... И собственно и в назначении ничего зазорного нет, т.к. все одно он будет первым (ID == 0), и в моде все же (дань анахронизмам) при бинде актора (actor_binder:reinit -> _G.idActor = self.object:id()) его уже назначенный ID корректируется с полученным от сервера (хотя это и явно излишне). И, кстати, обрати внимание на код самих разработчиков в том же _g.script: --' Проверка на инфопоршны, даже если игрока не существует function has_alife_info(info_id) local aa = alife() if aa == nil then return false end return aa:has_info(0, info_id) end - 0 - акторский ID, и подобное еще есть в ПЫС'вcких кодах. Изменено 8 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 8 Января 2012 (изменено) Ожидал бОльшего ... чем код интерфейса для доступа к хранилищу. По сути все то, что так или иначе уже используется в модах: спавн объекта для хранения (кто флешу, кто кпк, ... иль ящик), хранение (запись/чтение) данных в кастом-дате созданного объекта. Ограничение (<8 кБ) как было - так и осталось (для одного объекта), как понимаю ... :-( И даже более - доп.потери на перевод в строковый формат для кастом-даты. Если класс этого (эксклюзивного) объекта зарегистрировать в class_registrator.script и дописать стандартные методы, то ... можно ловить его серверную копию и забирать данные методами из созданного серверного класса в момент 'on_register' иль даже 'on_before_register', т.е. не дожидаясь спавна актора в игру. Изменено 8 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 8 Января 2012 Несколько неудобно неявно оппонировать, но раз это приводит к более спокойному обсуждению и высказыванию мнений, воспоьзуемя это формой. По хранилищу: Вероятно и авторитет автора, и заявленный им новаторский подход с барабаннаой дробью, и упоминание про расширение pstor'а ... были многими восприняты вероятно именно как нечто революционное. Но, кто-то не это ожидал от того что было выложено. Если говорить про критерий удобства использования данного хранилища, то в общем тут споров может быть мало, все на достаточно удобном и высоком уровне. О новациях в кодах можно уже не повторяться, в данном случае само хранилище (его коды) как раз то и не имеют своих новшеств, используя новации из хелперов и др. Достаточно удобно то, что как раз не нужно особенно заботиться о размере хранилища, подспавнивая по мере необходимости доп.объкты, а все делается автоманически на старте и при сэйве. Но вот как раз по недостаткам: Понятно, что можно писать и по старинке, проверенными временем вариантами, но(!) мы же в топике по программированию, поэтому: 1. Использование перебора всего диапазона игровых идентификаторов и выборка объектов по их секциям - все же уже достаточный анахронизм. Ведь зарегистрирован же новый класс с "CUST_ST" -> clsid.custom_storage и гораздо оперативнее идентифицировать объекты по именно нему, эксклюзивному числовому признаку. Вообще зачем перебирать объекты на старте? Они сами регистрируются (уже было упомянуто про on_register) и могут сами себя добавлять в читалку для хранилища. Аргумент про спавн актора как разрешение всему остальному ну никак не воспринимается. Было бы готово, а разрешать доступ к хранилищу можно хоть по какому флагу/коллбэку и т.п. Да и по большому счету, не обязательно читать отдельными читалками - определив в STATE_Read/STATE_Write наличие уже не фейкового идентификатора (65535), а реального (любого иного) - можно прямо из этого пакета обьъекта считать все данные, не занимаясь этим далее. (но это уже для тех кто в теме ...). 2. Конечно если считать достоинством неограниченность объектов для хранения, то можно пренебречь мелочевкою, но ... тут (или в соседнем топике) упоминалось, что достточно большая часть сохраняемых данных - числа. При чем числа разрядность. не в 4 байта, т.о. если старый-добрый метод упаковки кастом-даты подоптимизировать - вполне можно сэкономить кому-то "парочку" доп. объектов для хранения. (текстов это конечно не касается). А вот по пакетам: Тут нареканий побольше. И что-то не встречалось "заявили "то же самое, что было в АМК".", а было произнесено: "Перепев на новый лад с дополнениями прежнего ...", что далеко не одно и то же. Что режет глаза на фоне новшеств: 1. Достаточно "грязное" форматирование кодов/строк, с пропущенными начальными/конечными пробелами/табуляторами, перемежение пробелов/табуляторов, отсутствие или наличие пробелов между переменными/операторами/значениями ... Все же, если следовать чему-то одному - то и придерживаться этого. 2. На фоне выборки из таблиц (по clsid'ам) соседствуют и цепочки if ..., что при добавлении, например, тех же классов для аномалий из модов, только удлинит эти цепочки. 3. (спорно, но упомяну) Почти прежняя амк-гирлянда из функций типа r_q8v/r_l32u16v(/... или это дань соответствия имен функций и типов из ACDC? Но ничего не мешает создать табличку соответсвий. 4. О том, что нужно "проходиться рашпилем" говорил и имел ввиду в первую очередь достаточное кол-во нестыковок или невостребованностей из ACDC и для модуля работы с не-пакетами в игре. Если уж в некоторых сулчаях все же подставлять некие дефолтные значения (как для inventory_box), то зачем оставлять '$editor'? что заведомо приведет в игре, например для вертушек, к проблемам ... Не решил для себя пока считать ли достоинством использование ООП/классов для читалки/писалки нет-пакетов. То, что это новый подход - да, более ли он удобен - пока нет, если достоинства - да/нет, достаточно близко к ACDC - да. Но все же пакеты - не таймеры иль ивенты. (если нужно - можно продолжить и по конкретным ошибкам/неточностям/упущениям/...) "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 8 Января 2012 (изменено) Мда-а-а, похоже малейшая критика или указание на промахи/недостатки вызывает приступ ... отторжения и переходы на личность. Ну что же ... тоже оставляю топик, пусть остальные пообсуждают/повосхваляют. Изменено 8 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 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 "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 Доработанный вариант универсального хранилища 'xs_stor' свободного от некоторых ограничений. 1. Упрощен/оптимизирован процесс записи/чтения нет-пакетов объектов хранения. Никаких лишних операций. 2. Операция по сериализации хранимых в хранилище таблиц оставлена только непосредственно при сохранении таблий в объекты хранения., что дало возможность оптимизировать оперции чтеия/записи в собственно хранилище. Ссылка на народ.ру (~6.97 кБ). В архиве два скрипта, второй (se_stor) - проверенный в работе вариант от Симбиона с некоторыми нюансами (дан для сравнения). "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 (изменено) kamikazze, если ты не понял ни цели "для чего выложено", но того что же сделано, зачем же сразу наезжать? Мы тут обсуждаем или ругаем? Если говоришь о "злобных привнесенных косяков" - ну так и ткни носом, а не голословно ругай и ... подучи мат.часть вначале. 1. Собственно ничего и НЕ чиню, а делаю свою реализацию (считай сделал) на базе материалов xStream и собственных наработок. Как я понял, у malandrinus'а аналогичное. 2. Если тебе непонятно как работает - поясню, для этого и вложил второй рабочий на 100% вариант, чтобы если в первом упустил/очепятался, то по второму все можно проверить. Основное отличие моей реализации в том, что почти никаких внешних вмешательств в функционирование хранилища не требуется. 1. Само хранилице (массив) расположено в _G и готово к записи/чтению сразу по запуску игры. Естественно оно первоначально пустое. 2. По мере игры в хранилище поступают различные данные различных типов данных. Специально убрал сериализацию таблиц, дабы и не усекать возможносьт хранения в общем хранилище даже тех типов данных, которые не будут сохранены. Мне пока такое не требуется (привык не хранить в pstor'ах лишнего), но не вижу надобности в таком ограничение в самой игре. Само собою, в сэйвы невалидные типы данных не сохранятся. 3. По событию "сохранить данные из хранилища", которое я у себя делаю или по окончанию сэйва актора или даже по сэйву его КПК - включается цикл сохранения. Расписывать несколько объектов для сохранения нет смысла (пока) и рассмотрим один. - Создается объект и им порожденный tail-пакет, которому устанавливаются необходимые позиции чтения и записи. Именно этот подготовленный tai-пакет передается в менеджер сохранения данных хранилища. - В tail-пакет (со 2-ой позиции) записываются необходимые данные - до его заполнения (<8кБ) и пакет оставляется в покое(!), т.е. никаких принудительных операций сторонними нет-писалками с ним не делается. Хранилище сохраняет далее в следующий созданный аналогчный tail-пакет ... Обращаю внимание, что объекты хранения (кастом-стороджы) в это время никак не задействованы. - Если игра именно прекрашается, т.е. "* Saving objects..." - объекты хранения движком сохраняются, что вызывает для каждого метод STATE_Write, в который добавлен копировщих STATE_Copying. Эта связка приводит к тому, что штатным вызовом сохранения объекта убиваются оба зайца, сохраняется как пакет объекта, так и перезаписанный в него tail-пакет этого объекта(!). Т.к. позиции чтения выставлена и имеется конец записи - в объект копируются именно данные из хранилища. Все,объект(ы) сохранены. - Если был квик сэйв - то оставшиеся в игре объекты хранения могут быть баластом, который будет удален перед следующем сохранении и цикл повторится заново или их можно удалить по таймеру через неск. секуннд. Хранилище в это времы продолжает работу в штатном режиме. Теперь о заполнении хранилища при старте игры: - Т.к. имеется серверный класс 'se_custom_storage' с его on_register и STATE_Read + STATE_Copying. В момент загрузки объектов в иру ("* Loading objects...") загружаются и сторадж-объекты. Для каждого создается tail-пакет и движком вызывается STATE_Read. При этои из нетпакета объекта в tail-пакет считываются ранее сохраненные данные хранилища. - по on_register tail-пакет передается считывателю данных в хранилище. Опять, никакие сторонние манипуляции сторонними нет-писалками не требуются. Т.о. хранилще заполняется ранее сохраненными данными по факту появления в игре каждого объекта хранения и ПОЛНОСТЬЮ восстановлено к моменту подключения сервера к игре (Сервер: Соединяемся...). С этого момента хранилище готово выдавать все те данные, которые были в нем в момент сохранения в сэйв. Хочешь ждать спавна актора - твое дело, но можно и не дожидаться, а еще до его спавна пользоваться сохраненными данными. Восстанавливать настройки модулей, таймеры и пр. ... Так что и сам уже пользуюсь и думаю и другие будут пользоваться! Изменено 11 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 По поводу сериализации уже дали реплику. Зачем ограничивать само хранилище, когда это делать нужно только для объектов хранения! Ну а если нужно кому в процессе игры подобное контролировать, то не таким же драконовским способои и конечно же опционально. "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 (изменено) Это проверяется при сохранении и получаем ошибку Не получаем ошибку, а получаем предупреждение, кпо которому можно при необходимости и абортнуть. Это все же разница. То, что не должно попасть в сэйвы - не попадет, и это забота модмейкера. Кому-то на такие данные и наплевать, а кто-то озаботится и сам не будет хранить в общем хранилище непотребное. Ну а озабоченным ничего не мешает два типа set'ов себе сделать: обычный и с сериализацией (типа set_safe) и будет ему счастье отлавливать баги. Зачем же боясь аварии всегда ехать со скоростью 40 км/час, когда штатно можно можно и под 80 .. ;-) Изменено 11 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 (изменено) Уж если разговор о работе с хранилищем, а не о сохранении записи, то ... мною НЕ используется сериализатор, а используется компрессор-сериализатор, что дает (даст) выигрыш более 40% на каждый объект хранения. Т.о. таблица, не влезающая в 8кБ сериализатром, вполне может быть сохранена без усечений и абортов игры. И давайте не подменять понятия. Все же забота о том, чтобы в хранилище не поступали заведомо большие куски - это забота модмейкера, а не хранилища. Мне вообще непонятно какие же объемы и чего могут привести к переполнению даже одного сторадж-объекта. Ну а с компрессией - нужно постараться. Хранилище должно надежно функционировать и не давать сбоев, а не заниматься воспитанием модмейкера. И, кину камень в ваш огород: В моем варианте в хранилище не попадут ни большие таблицы, ни большие строки, проверка длины которых в ваших вариантах с сериализатором НЕ предусмотрена и это (большая строка) обрушит объект хранения (сэйв)! Конечно сохранять строку более 8кБ врядли кому понадобится, но ... давайте уж последовательно. Озаботились предупреждать нерадивых о таблицах, так чем же строки вам глянулись и не требуют проверки? ;-) Изменено 11 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 (изменено) kamikazze, все же жду ответа о "злобных привнесенных косяков", на то и запостил, чтобы может быть упущенное выяснить и исправить. Andrey07071977, да, компрессор несколько уже доработанный, но этого плана. Чуть его подправил и логику выдачи ошибки привнес. P.S. Компрессор-сериализатор выложу, но требуется время, т.к. он у меня в мод вшит. Причешу под внешнее использование (день/два) и выложу, т.к. связка получается очень неплохой. Перенос сохранения тайников из pstor'а актора в састом-сторадж дал выигрыш, освободив от забот по переполнению акторского, но ... просто сериализатор, где булевы значения вместо одного байта по 4-5 забирают - трастранжиривание. В тайниках 2/3 - это булевы. Так что компрессор ужимает на 70% от сериализатора. Изменено 11 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 11 Января 2012 (изменено) kamikazze, ну наверное за широкую спину прекрасного пола не следует отступать, сказав что-то от себя и ... "злобный косях", как я понял это один пока, что при эффемерной вероятности кого-то нерадивого засунуть в хранилище табличку с четырех-томником "войны и мира", такой чудак не получит отлупа? Да, косяк ... хотя в выложенном тут Вашем варианте этот недотепа ничего все одно не увидит, даже в логе. Сорри, но для меня этот "косяк" скорее достоинство. Ну а то, что компрессия конечно же несколько больше ресурсов требует - так и делается она единожды на сэйв и пока при моих стрес-тестах не смог отличить от иных вариантов. Зато выигрыш 8кБ +40% - немалая компенсация (ИМХО). Ну а остальной (к паре) какой косяк? Давайте НЕ хаять не свое, а обсуждать и вырабатывать или нечто обобщенное для других или учиться друг у друга, признавая и свои ошибки и чужие удачные решения. P.S. Ну а по поводу "ошибок" копресии и пр. - тут уже была разборка ... неплохо бы тебе почитать. 1. Смею тебя заверить, что за уже более чем годичную практику использования в моде каких-либо коллизий из-за компресии не обнаружено ни в теории ни на практике, хотя гоняется в моде в хвост и гриву. 2. Ну по метатаблицам - вот как раз выше и писал, что и подработан немного, и пример от xStream, конечно же полезен и будет встроен (уже встроен вчерне). Добавлено через 59 мин.: malandrinus, xStream, просьба уточнить/поправить строку ниже, т.е. чье авторство (или совместное) на упомянутое ниже: ;--/ by Malandrinus & xStream CUST_ST = custom_storage, cse_alife_dynamic_object ;se_custom_storage Эти данные нужно бы дать KD87 для внесение в коды универсального ACDC, дабы учитывались для работы с секциями/классами для all.spawn'а. P.P.S. Спасибо за пояснение. Т.к. в первичным по времени был xs_stor и в нем не достаточно ясный thanx - поэтому и решил уточнить. Цель - же 'занятые' наменования секций и/или clsid'ов, указанные в общем для многих инструменте (ACDC), могут помочь в совместимости модов и/или исключить возможные пересечения в названиях. Само собою, что в all.spawn'e ничего по стораджу не должно быть (не требуется), но универсальный ACDC сканирует конфиги и наткнувшись на секцию "custum_storage" с неизвестным классом (class = CUST_ST) - возникает хотя и не фатальная, но неопределенность и соотв.запись в конфиге секций. Поэтому и возможно занесение в его конфиг (иль скрипты), для однозначности и определенности соответствий секций классам. Также, в модуле m_net_utils (а это уже в игре/скриптах) также используется предварительная идентификация всех классов и наименования, с указанием источника/авторства, упоминаются в конфиге. Изменено 11 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 12 Января 2012 (изменено) Несколько замечаний несущественного характера: 1. Для "net_alife_item_weapon_shotgun" параметр 'ammo_ids' в игре хранит кол-во идентификаторов типов заряженных патронов, после которого идет список этих идентификаторов типов патронов, который может быть восстребован модмейкерами, но в нынешнем виде считывается в бинарный остаток (tail). 2. Для "net_respawn" параметр 'spawned_objects' (в игре и ACDC - 'spawned_obj') хранит кол-во игровых идентификаторов заспавненных объектов, после которого идет собственно их список. Так же может быть восстребован для чистки или иного ... 3. Вероятно стОит выкинуть определение в классе 'net_base' незадействованных переменных 'rssz', 'rusz', 'wusz'. Добавлено через 8 мин.: abramcumner, ИМХО как раз наименование 'binder' вполне к месту. Большинство привыкло биндерами называть биндеры клиентских объектов, хотя серверные объекты имеют аналогичные биндеры (которые, кстати, первичны) с аналогичными методами. Просто, при необходимости, нужно по контексту добавлять уточняющее: клиентский/серверный биндер иль биндер серверного объекта ... И с 'unused_pad' ты что-то не то сказал. Никак 'unused_pad', который в "net_abstract", не может иметь отношение к object_binder:save/load ... Изменено 12 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 13 Января 2012 (изменено) Наверное стОит обсуждать суть, а не форму, по которой (форме) у каждого свои предпочтения, и тем более дано пояснение: xStream: ЗЫ Да, лакончино, скомкано и т.п. Все будет развернуто, когда доберусь до мануала. А пока тот, кто понимает, прошу вас это посмотреть, потестить. Нужен фидбек по ошибкам - что достаточно понятно о 'форме' ... Вот по сути далеко не все ясно. - есть достаточно сильная зависимсость от класса 'custom_data' из хелпера, что снижает гибкость использования (ИМХО); - есть сомнения, что в данном виде сможет быть применено/портировано в CS/SCoP ... имею ввиду не измененные параметры классов, а использование локальных копий технологических нет-пакетов; - достачно неудобная (ИМХО) семантика вызовов, когда требуется постоянно справляться в ACDC и додумывать имена классам ... Как любитель ковыряться в заинтересовавших кодах, пока не даю фидбека, т.к. на искусственных тестах и не интересно и малоинформативно, а в игру/мод встроить (не нарушая остального) требуется некоторое время (еще денек-другой). Вот тогда погоняем по полной программе. Изменено 13 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 13 Января 2012 (изменено) Согласен с точкой зрения malandrinus'а на 'net_dummy' в игре. Конечно их полезность может быть в каких-то локальных случаях/проверках, но не как общий принцип. По нюансам нет-пакетов в ЗП/ЧН: Есть подозрение, что в этих версиях ограниченно число копий объектов, выдаваемых функцией net_packet(). Т.о. в процессе игры возникают ситуации, когда по такому запросу пакета возвращается 'уже использованный' ранее объект, т.е. с не нулевыми позициями чтения/записи. Если в читалке/писалке нет-пакетов в подобных ситуациях не контролируются позиции чтения/записи, то ... можно убежать в конец заведомо ложных (чужих) данных ... Ну а по семантике вызовов, то все же стоЮ на позиции: достаточно дать на вход объект, и забота читалки/писалки идентифицировать этот объект с нужным классом, а не отдавать это на откуп модмейкеру иль сторонним скриптам. Так, хотя и чуток излишних действий требуется, но и контроль от ошибок/очепяток полный (и не особенно опытным модмейкерам поменьше заботы с именованием своих вызовов). P.S. Вот по работе с клиентcкими объектами не определился ... не думаю, что это однозначно хорошо/плохо. Привычка: всегда проверять результат вызова, поэтому если не будет возвращен 'net_dummy', то и дальнейшие операции прекращаю. Т.о. передача проверки наличия серверного объекта по клиентскому - вполне разумна и разгружает коды вызовов получения пакетов. Да и не факт, что какой-то "невнимательный" не передаст на вход не существующий серверный объект, а устаревший линк на него. Т.о. внутренняя перепроверка, хотя и чуток затратна, но исключает какие-либо коллизии. То, что это расхолаживает программиста - не совсем согласен именно в данном контексте, т.е. применительно к модмейкерам игры. Это все же в основном большинстве не программисты, а любители и помимо воспитания, все же и облегчения их "труда" восстребованы. ;-) (но это уже конечно за рамками этого топика) Изменено 13 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 14 Января 2012 (изменено) xStream, по поводу 'skeleton_flags': На своем варианте читалки-писалки встроиз пару дней назад обработку этого параметра, но сильно не гонял, хотя и ошибок не заметил. Вот портированный вариант под xs_netpk (может сгодится): function net_ph_skeleton:__init(obj) super(obj) self.name = "cse_ph_skeleton" --/ for packet dumping self.bases = { } self.props = { { name = 'skeleton_name', type = 'sz', default = '$editor' }, { name = 'skeleton_flags', type = 'u8', default = 0 }, { name = 'source_id', type = 'h16', default = -1 } } self.updprops = { { name = 'skeleton', type = 'skeleton' } --/#!# } end function net_ph_skeleton:_read(ret,stpk,updpk) self:_read_bases(ret,stpk,updpk) if self.skip ~= skip_type.state then for _,prop in ipairs(self.props) do ret[prop.name] = this["_r_"..prop.type](stpk, prop.len) --/ can use ret.skeleton_flags because it is already read if prop.name == "skeleton_flags" and bit_and(ret.skeleton_flags, 4) == 4 then if self.skip ~= skip_type.update then self:_read_updprops(ret,stpk,updpk) end end -- end end --[[ --/ или так: if self.skip ~= skip_type.update then if ret.skeleton_flags and bit_and(ret.skeleton_flags, 4) == 4 then self:_read_updprops(ret,stpk,updpk) end end --]] end - для записи (_write) по аналогии. гонял в игре совсем мало, поэтому ошибок пока не заметил но и статистики нет. P.S. Забыл добавить: в код писалки '_w_skeleton' вставить строку: pk:w_u16(#val.bones) Изменено 14 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 14 Января 2012 (изменено) xStream, к сожалению тоже пока не имею информации 'как должно быть' (или хотя бы в какой части). По ранним репликам уже задавал вопрос, но пока уточнения нет. Пробовал и пробую методом тыка в state и update. Но, и из-за малого времени проверок и ... как поймать то такой объект(?) в игре не совсем ясно - пока неяснось 'правильно иль нет'. Сегодня попробую набрать статистики и может можно будет сделать вывод. Изменено 14 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 14 Января 2012 xStream, в писалке '_w_skeleton' все же не добавила: pk:w_u16(#val.bones) --/< bones_count function _w_skeleton(pk, val) for i=1,4 do --/ u16x4 == u64 pk:w_u16(val.bones_mask[i]) end pk:w_u16(val.root_bone) pk:w_vec3(val.ph_angular_velosity) pk:w_vec3(val.ph_linear_velosity) pk:w_u16(#val.bones) --/< bones_count for _,bone in ipairs(val.bones) do _w_q8v(pk,bone.ph_position) _w_q8v(pk,bone.ph_rotation) pk:w_u8(bone.enabled) end end или я ошибаюсь? (сам пока пытаюсь методом тыка ...) Также в классах:"net_alife_mounted_weapon" и "net_alife_object" ошибка с 'self.object', т.к. он нигде не определяется. "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 14 Января 2012 xStream, в "net_base" запоминается под self.obj, а не self.object ... А по self.skip не стал ничего писать, т.к. в своем варианте (в моде) использую управляемое получение пакета, т.е. если НЕ потребно читать весь (c update) - то и читается только state. Это и чуть ускоряет и экономит ресурсы и снижает вероятность ошибок при полной перезаписи пакетов, тогда, когда нужно порою всего-то задать типа story_id. Т.о. флаг 'skip' передаю (если требуется) уже в вызов при получении пакета (полного или частичного) и соответственно протрассировал его по классам. "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение
Artos 99 Опубликовано 16 Января 2012 (изменено) xStream, вот вариант (проверенный в игре) для 'skeleton' в формате ACDC и без внешней функции: --/ ----------------------------------------------------------------- class "net_cse_ph_skeleton" (net_base) --/ has derived --/ ----------------------------------------------------------------- function net_cse_ph_skeleton:__init(obj,skip) super(obj, bit_or(skip, skip_props.update), "cse_ph_skeleton") self.bases = {} self.props = { { name = 'skeleton_name', type = 'sz', default = '$editor' }, { name = 'skeleton_flags', type = 'u8', default = 0 }, { name = 'source_id', type = 'h16', default = -1 } } self.props_bones = { --/#+# { name = 'bones_mask', type = 'q8v', default = {0,0,0,0,0,0,0,0}, len = 8 }, --/'u8v8' { name = 'root_bone', type = 'u16', default = 0 }, { name = 'ph_angular_velosity', type = 'f32v3', default = vector() }, { name = 'ph_linear_velosity', type = 'f32v3', default = vector() }, { name = 'bones_count', type = 'u16', default = 0 } --/#?# 'bone_count' } self.props_bone = { --/#+# { name = 'ph_position', type = 'q8v', default = {0,0,0}, len = 3 }, --/'q8v3' { name = 'ph_rotation', type = 'q8v', default = {0,0,0,0}, len = 4 }, --/'q8v4' { name = 'enabled', type = 'u8', default = 1} } self.updprops = {} end function net_cse_ph_skeleton:_read(ret,stpk,updpk) self:_read_bases(ret,stpk,updpk) if self.skip ~= skip_props.state then for _,prop in ipairs(self.props) do ret[prop.name] = this["_r_"..prop.type](stpk, prop.len) end if bit_and(ret.skeleton_flags, 4) == 4 then --/ check 'skeleton' for _,prop in ipairs(self.props_bones) do --/ read 'bones' if ret[prop.name] ~= 'bones_count' then ret[prop.name] = this["_r_"..prop.type](stpk, prop.len) end end if ret.bones_count > 0 then --/ read 'bone': ... ret["bones"] = {} for i=1,ret.bones_count do local bone = {} for _,prop in ipairs(self.props_bone) do bone[prop.name] = this["_r_"..prop.type](stpk, prop.len) end table.insert(ret["bones"], bone) end end end end end function net_cse_ph_skeleton:_write(data,stpk,updpk) self:_write_bases(data,stpk,updpk) if self.skip ~= skip_props.state then for _,prop in ipairs(self.props) do if prop.name == "skeleton_name" and self.obj and (self.obj:clsid() == clsid.script_heli or self.obj:clsid() == clsid.car_s) and data.skeleton_name == prop.default then this["_w_"..prop.type](stpk, "idle") --/#!# change '$editor' for cars & helicopters else this["_w_"..prop.type](stpk, data[prop.name] or prop.default or 0) end end if bit_and(data.skeleton_flags, 4) == 4 and data.bones_count then --/ check 'bones' for _,prop in ipairs(self.props_bones) do --/ write 'bones' this["_w_"..prop.type](stpk, data[prop.name] or prop.default or 0) end if data.bones then --/ write 'bone' ... for _,bone in ipairs(data.bones) do for _,prop in ipairs(self.props_bone) do this["_w_"..prop.type](stpk, bone[prop.name] or prop.default or 0) end end end end end end --/ ----------------------------------------------------------------- Также, может быть чем-то глянется вариант компоновки для типовых чанков и CTime: --/ ------------------------- --/ prop.type: variable chunk:: in: datatype: u8|u16|s32|floor|bool|stringZ|... [default: nil=>'u8' (по-байтно)] --/ ------------------------- --/ read:: in: packet, lenght [,datatype [,table]] | out: table local _r_chunk = function(pk,len,dt,tbl) if len and len > 0 then if not (tbl and type(tbl) == 'table') then tbl = {} end local func = this["_r_"..(dt or 'u8')] for i=1,len do table.insert( tbl, func(pk) ) end end return tbl or {} --/> end --/ write:: in: packet, table [,datatype] local _w_chunk = function(pk,tbl,dt) if type(tbl) == 'table' and next(tbl) then local func = this["_w_"..(dt or 'u8')] for _,v in ipairs(tbl) do func(pk,v) end end end --/ ------------------------- --/ prop.type: fixed chunks --/ ------------------------- _r_q8v = function(pk,len) return _r_chunk(pk,len, 'u8' ) end _r_u8v8 = function(pk) return _r_chunk(pk,8, 'u8' ) end --/#?# => 'r_ctime' _r_l32u16v = function(pk) return _r_chunk(pk,pk:r_s32(), 'u16') end _r_l32u8v = function(pk) return _r_chunk(pk,pk:r_s32(), 'u8' ) end _r_l8u16v = function(pk) return _r_chunk(pk,pk:r_u8 (), 'u16') end _r_l8u8v = function(pk) return _r_chunk(pk,pk:r_u8 (), 'u8' ) end _r_l16u8v = function(pk) return _r_chunk(pk,pk:r_u16(), 'u8' ) end _w_q8v = function(pk,tbl) _w_chunk(pk,tbl,'u8' ) end _w_u8v8 = function(pk,tbl) _w_chunk(pk,tbl,'u8' ) end --/#?# => 'w_ctime' _w_l32u16v = function(pk,tbl) pk:w_s32(#tbl) _w_chunk(pk,tbl,'u16') end _w_l32u8v = function(pk,tbl) pk:w_s32(#tbl) _w_chunk(pk,tbl,'u8' ) end _w_l8u16v = function(pk,tbl) pk:w_u8 (#tbl) _w_chunk(pk,tbl,'u16') end _w_l8u8v = function(pk,tbl) pk:w_u8 (#tbl) _w_chunk(pk,tbl,'u16') end _w_l16u8v = function(pk,tbl) pk:w_u16(#tbl) _w_chunk(pk,tbl,'u8' ) end --/ ------------------------- --/ prop.type: CTime --/ ------------------------- _r_ctime = function(pk) tbl.ctime = utils.r_CTime(pk) --/< userdata|0|nil return tbl --/> end _w_ctime = function(pk,tbl) if type(tbl.ctime) == 'userdata' then utils.w_CTime(pk, tbl.ctime) elseif iSSVer < 8 then --/ SHoC pk:w_u8(0) elseif not tbl.ctime then --/ CS|CoP pk:w_u8(-1) else utils.w_CTime(pk, game.CTime()) --/#?# TODO: проверить end end --/ ------------------------- (примечание: некоторые вкрапления в кодах - особенность мода, а не для общего употребления) Изменено 16 Января 2012 пользователем Artos "Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени Поделиться этим сообщением Ссылка на сообщение