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

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


Malandrinus

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

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ких кодах.

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

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

Поделиться этим сообщением


Ссылка на сообщение

Ожидал бОльшего ... чем код интерфейса для доступа к хранилищу.

По сути все то, что так или иначе уже используется в модах: спавн объекта для хранения (кто флешу, кто кпк, ... иль ящик), хранение (запись/чтение) данных в кастом-дате созданного объекта. Ограничение (<8 кБ) как было - так и осталось (для одного объекта), как понимаю ... :-( И даже более - доп.потери на перевод в строковый формат для кастом-даты.

Если класс этого (эксклюзивного) объекта зарегистрировать в class_registrator.script и дописать стандартные методы, то ... можно ловить его серверную копию и забирать данные методами из созданного серверного класса в момент 'on_register' иль даже 'on_before_register', т.е. не дожидаясь спавна актора в игру.

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

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

Поделиться этим сообщением


Ссылка на сообщение

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

 

По хранилищу:

Вероятно и авторитет автора, и заявленный им новаторский подход с барабаннаой дробью, и упоминание про расширение 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

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

Поделиться этим сообщением


Ссылка на сообщение

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

 

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

 

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

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

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

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

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

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

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

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

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

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

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

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


--private members

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

 

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

 

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

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

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

Поделиться этим сообщением


Ссылка на сообщение

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

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

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

 

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

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

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

Поделиться этим сообщением


Ссылка на сообщение

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-пакет передается считывателю данных в хранилище. Опять, никакие сторонние манипуляции сторонними нет-писалками не требуются.

Т.о. хранилще заполняется ранее сохраненными данными по факту появления в игре каждого объекта хранения и ПОЛНОСТЬЮ восстановлено к моменту подключения сервера к игре (Сервер: Соединяемся...). С этого момента хранилище готово выдавать все те данные, которые были в нем в момент сохранения в сэйв.

Хочешь ждать спавна актора - твое дело, но можно и не дожидаться, а еще до его спавна пользоваться сохраненными данными. Восстанавливать настройки модулей, таймеры и пр. ...

Так что и сам уже пользуюсь и думаю и другие будут пользоваться!

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

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

Поделиться этим сообщением


Ссылка на сообщение

По поводу сериализации уже дали реплику.

Зачем ограничивать само хранилище, когда это делать нужно только для объектов хранения!

Ну а если нужно кому в процессе игры подобное контролировать, то не таким же драконовским способои и конечно же опционально.

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

Поделиться этим сообщением


Ссылка на сообщение
Это проверяется при сохранении и получаем ошибку

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

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

 

Ну а озабоченным ничего не мешает два типа set'ов себе сделать: обычный и с сериализацией (типа set_safe) и будет ему счастье отлавливать баги.

Зачем же боясь аварии всегда ехать со скоростью 40 км/час, когда штатно можно можно и под 80 .. ;-)

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

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

Поделиться этим сообщением


Ссылка на сообщение

Уж если разговор о работе с хранилищем, а не о сохранении записи, то ... мною НЕ используется сериализатор, а используется компрессор-сериализатор, что дает (даст) выигрыш более 40% на каждый объект хранения. Т.о. таблица, не влезающая в 8кБ сериализатром, вполне может быть сохранена без усечений и абортов игры.

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

Мне вообще непонятно какие же объемы и чего могут привести к переполнению даже одного сторадж-объекта. Ну а с компрессией - нужно постараться.

Хранилище должно надежно функционировать и не давать сбоев, а не заниматься воспитанием модмейкера.

 

И, кину камень в ваш огород: В моем варианте в хранилище не попадут ни большие таблицы, ни большие строки, проверка длины которых в ваших вариантах с сериализатором НЕ предусмотрена и это (большая строка) обрушит объект хранения (сэйв)!

Конечно сохранять строку более 8кБ врядли кому понадобится, но ... давайте уж последовательно. Озаботились предупреждать нерадивых о таблицах, так чем же строки вам глянулись и не требуют проверки? ;-)

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

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

Поделиться этим сообщением


Ссылка на сообщение

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

 

Andrey07071977, да, компрессор несколько уже доработанный, но этого плана. Чуть его подправил и логику выдачи ошибки привнес.

 

P.S. Компрессор-сериализатор выложу, но требуется время, т.к. он у меня в мод вшит. Причешу под внешнее использование (день/два) и выложу, т.к. связка получается очень неплохой. Перенос сохранения тайников из pstor'а актора в састом-сторадж дал выигрыш, освободив от забот по переполнению акторского, но ... просто сериализатор, где булевы значения вместо одного байта по 4-5 забирают - трастранжиривание. В тайниках 2/3 - это булевы. Так что компрессор ужимает на 70% от сериализатора.

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

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

Поделиться этим сообщением


Ссылка на сообщение

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 (а это уже в игре/скриптах) также используется предварительная идентификация всех классов и наименования, с указанием источника/авторства, упоминаются в конфиге.

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

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

Поделиться этим сообщением


Ссылка на сообщение

Несколько замечаний несущественного характера:

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 ...

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

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

Поделиться этим сообщением


Ссылка на сообщение

Наверное стОит обсуждать суть, а не форму, по которой (форме) у каждого свои предпочтения, и тем более дано пояснение:

xStream: ЗЫ Да, лакончино, скомкано и т.п. Все будет развернуто, когда доберусь до мануала. А пока тот, кто понимает, прошу вас это посмотреть, потестить. Нужен фидбек по ошибкам
- что достаточно понятно о 'форме' ...

Вот по сути далеко не все ясно.

- есть достаточно сильная зависимсость от класса 'custom_data' из хелпера, что снижает гибкость использования (ИМХО);

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

- достачно неудобная (ИМХО) семантика вызовов, когда требуется постоянно справляться в ACDC и додумывать имена классам ...

 

Как любитель ковыряться в заинтересовавших кодах, пока не даю фидбека, т.к. на искусственных тестах и не интересно и малоинформативно, а в игру/мод встроить (не нарушая остального) требуется некоторое время (еще денек-другой). Вот тогда погоняем по полной программе.

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

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

Поделиться этим сообщением


Ссылка на сообщение

Согласен с точкой зрения malandrinus'а на 'net_dummy' в игре. Конечно их полезность может быть в каких-то локальных случаях/проверках, но не как общий принцип.

 

По нюансам нет-пакетов в ЗП/ЧН:

Есть подозрение, что в этих версиях ограниченно число копий объектов, выдаваемых функцией net_packet(). Т.о. в процессе игры возникают ситуации, когда по такому запросу пакета возвращается 'уже использованный' ранее объект, т.е. с не нулевыми позициями чтения/записи.

Если в читалке/писалке нет-пакетов в подобных ситуациях не контролируются позиции чтения/записи, то ... можно убежать в конец заведомо ложных (чужих) данных ...

 

Ну а по семантике вызовов, то все же стоЮ на позиции: достаточно дать на вход объект, и забота читалки/писалки идентифицировать этот объект с нужным классом, а не отдавать это на откуп модмейкеру иль сторонним скриптам. Так, хотя и чуток излишних действий требуется, но и контроль от ошибок/очепяток полный (и не особенно опытным модмейкерам поменьше заботы с именованием своих вызовов).

 

P.S. Вот по работе с клиентcкими объектами не определился ... не думаю, что это однозначно хорошо/плохо.

Привычка: всегда проверять результат вызова, поэтому если не будет возвращен 'net_dummy', то и дальнейшие операции прекращаю. Т.о. передача проверки наличия серверного объекта по клиентскому - вполне разумна и разгружает коды вызовов получения пакетов.

Да и не факт, что какой-то "невнимательный" не передаст на вход не существующий серверный объект, а устаревший линк на него. Т.о. внутренняя перепроверка, хотя и чуток затратна, но исключает какие-либо коллизии.

То, что это расхолаживает программиста - не совсем согласен именно в данном контексте, т.е. применительно к модмейкерам игры. Это все же в основном большинстве не программисты, а любители и помимо воспитания, все же и облегчения их "труда" восстребованы. ;-) (но это уже конечно за рамками этого топика)

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

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

Поделиться этим сообщением


Ссылка на сообщение

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)

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

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

Поделиться этим сообщением


Ссылка на сообщение

xStream, к сожалению тоже пока не имею информации 'как должно быть' (или хотя бы в какой части). По ранним репликам уже задавал вопрос, но пока уточнения нет. Пробовал и пробую методом тыка в state и update.

Но, и из-за малого времени проверок и ... как поймать то такой объект(?) в игре не совсем ясно - пока неяснось 'правильно иль нет'.

Сегодня попробую набрать статистики и может можно будет сделать вывод.

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

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

Поделиться этим сообщением


Ссылка на сообщение

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', т.к. он нигде не определяется.

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

Поделиться этим сообщением


Ссылка на сообщение

xStream, в "net_base" запоминается под self.obj, а не self.object ...

А по self.skip не стал ничего писать, т.к. в своем варианте (в моде) использую управляемое получение пакета, т.е. если НЕ потребно читать весь (c update) - то и читается только state. Это и чуть ускоряет и экономит ресурсы и снижает вероятность ошибок при полной перезаписи пакетов, тогда, когда нужно порою всего-то задать типа story_id. Т.о. флаг 'skip' передаю (если требуется) уже в вызов при получении пакета (полного или частичного) и соответственно протрассировал его по классам.

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

Поделиться этим сообщением


Ссылка на сообщение

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
--/ -------------------------

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

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

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

Поделиться этим сообщением


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

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