proper70 74 Опубликовано 14 Сентября 2014 В дополнение к таблице _G: Если через кейлоггер или еще как-то вызвать команду _G[имя скрипта] = nil, то движок вновь перекомпилирует указанный скрипт при первом к нему обращении. Что это дает? Это дает то, что если вы отлаживаете какой-либо код, то нет необходимости делать сейв/лоад после каждой правки. Если мы, допустим, правим файл amk.script, то достаточно после сохранения правок через кейлоггер вызвать _G.amk = nil и все - движок перекомпилирует его по новой и все изменения тут же вступят в силу без очередного сейв/лоада. Жаль, что эту фичу я для себя открыл только сейчас. Сколько времени можно было бы сэкономить. Это работает для всех файлов кроме биндеров. Дело в том, что для каждого объекта биндер зачем-то компилируется отдельно, поэтому чтобы увидеть изменения, например в bind_monster.script, то нужно написать _G.bind_monster = nil и заспавнить нового монстра. И на новом объекте изменения уже будут применены. 1 7 Поделиться этим сообщением Ссылка на сообщение
proper70 74 Опубликовано 13 Марта 2015 (изменено) Несколько раз порывался написать этот пост, да все откладывал. Наконец нашел время. Хотя глобальное модостроение и пришло в свою завершающую фазу, но все же, напишу то, что считаю нужным, может вдруг кому и пригодится. Речь пойдет об оптимальных со всех точек зрения и построенных на правилах объектно ориентированного программирования конструкциях, как я их вижу (уверен, далеко не самый лучший вариант, но все же), которые должны были бы быть в скриптах сталкера, но которых увы, нет. Предлагаемый мной вариант организации кода позволил бы очень сильно уменьшить размер конфигов и скриптов, а также достаточно ощутимо оптимизировал бы их использование и выполнение. Жаль, что это все я понял только тогда, когда ОП-2 уже был готов и зарелизен) Можно было бы сэкономить кучу времени и нервов, отлаживая и доводя до ума все это) Да и переделывать уже имеющееся никто не будет, но эта инфа вполне может пригодиться в будущих скриптах. Начнем с класса ini_file - чтения конфигов. Огромная проблема в том, что он вылетает при каждой попытке прочесть отсутстувующую секцию или параметр. И чтобы решить эту проблему, многие мододелы, начиная с ПЫСов, нагородили разных оберток - функций, которые закрывают этот вылет, но при этом очень усложняют собственно сам процесс чтения. Тут не нужно было не городить всякие "примочки" "обертки" "проверки" и прочее, а просто сделать, например, вот так: class script_ini (ini_file)function r_string(section, line)if ini_file.section_exist(self, section) and ini_file.line_exist(self, section, line) thenreturn ini_file.r_string(self, section, line)elsereturn ""endendИ т.д. переопределить все функции, прописав в них возврат значения по умолчанию при отсутствии секции либо параметра, и все дальнейшие скрипты строить исходя из этого. И тогда не нужно было плодить всяких utils.cfg_get_string в логике, и тот ужас, который мы имеем в Солянке в виде getIniValueString. Достаточно было в _G.script написать вот это:ini = script_ini(system_ini())И далее, во всех скриптах писать просто:local var = ini:r_string(section, line)if var == нужное значение thenИ все. Никаких вылетов при отсутствии секции или параметра не будет. Конечно, при правильной организации кода эти фишки должны были быть встроены в оригинал класса ini_file изначально, но раз их нет, то их надо было просто добавить скриптово)Также, для удобочитаемости и удобоиспользования: вместоtreasure_manager.get_treasure_manager():give_treasure("mil_borov_secret")написать в опять же в _G.script:treasure = treasure_manager.CTreasure()И в любых вызовах использоватьtreasure:give("mil_borov_secret")Соответсвенно сократив имена методов класса. Ведь этот класс все равно создается единожды при лоаде и хранится в памяти всю игру. Аналогично поступить для всех глобальных классов.Следующее. Просто напрашивается всеми фибрами души вместо amk.start_timer сделать опять же в _G:timer = amk.CTimer(name, delay, parameters)B далее у этого класса методы start, g_start, has, stop, execute (при наступлении времени) и любые другие.И, наконец, самое нужное, на мой взгляд, и самое опитимизирующее практически все скрипты изменение:class CLogicИ в нем методы: net_spawn (аналог set_scheme+assign_storage_and_bind+subscribe_action_for_events), update (аналог pick_section_from_condlist+try_switch_to_another_section - все апдейты), net_destroy (аналог deactivate+reset_scheme подключенных схем)Далее, переопределяем object_binder:class "script_object_binder" (object_binder)function script_object_binder:__init(obj) super(obj)self.object.logic = CLogic() -- здесь можно добавить проверки, есть ли логика у объекта, и при каких условиях ее надо подключать.endfunction script_object_binder:net_spawn(data)object_binder.net_spawn(self, data)self.object.logic:net_spawn()endfunction script_object_binder:update(delta)object_binder.update(self, delta)self.object.logic:update(delta)endfunction script_object_binder:net_destroy()object_binder.net_destroy(self)self.object.logic:net_destroy()endИ т.д. И затем глобализируем этот переопределенный класс в _G:object_binder = script_object_binder()И тогда в биндерах объектов ничего править не нужно - все само переопределится - логика всегда будет подключаться полностью автоматически. Просто идеальный вариант, но увы, с логикой имеем только то, что имеем)) Движок при каждом старте выполняет команду ini_file("system.ini"). то есть он вычитывает и строит в памяти всю таблицу подключенных секций и параметров. Ссылка на эту таблицу возвращается функцией system_ini(). Причем все секции и параметры упорядочены по алфавиту (это лекго увидеть, выведя в лог список параметров любой секции через r_line), и, судя по всему, проиндексированы, так как получение любого параметра происходит очень быстро (можете сами проверить, запустив цикл на миллион чтений и замерив время выполнения). Другими словами - к файлу на диске движок при чтении прараметра из конфига никак не обращается - все читается один раз при старте. Следовательно, всякие дублирования данных из конфигов в таблицах, а также предубеждения, что system_ini() нельзя читать на апдейте, ибо это тормоз - абсолютно неверны. Наоборот, не только можно, но и даже нужно использовать чтение напрямую из конфигов вместо создания дублирующих таблиц. Конечно, при условии определения разовой, глобальной переменной, описаной выше:ini = system_ini()А не в каждом скрипте создавать свою копию.Исходя из вышеизложенного, наиболее оптимальный вариант работы с конфигами будет примерно следующим. Покажу на примере task_manager.CRandomTask:__init(). В ней достаточно прочитать в локальные таблицы и хранить в памяти постоянно только вот это:curr_task_info.type = self.task_ini:r_string(id, "type")curr_task_info.name = idА все остальное вычитывать на лету при активации диалога с вендором. Тогда в памяти будут храниться только реально нужные данные, по которым ведется поиск и отбор. А всё остальное - значения секций - можно прочесть "на лету". Это исключает дублирование данных движком и скриптами. Простой пример. Все монстры имеют общий базовый конфиг monster_base:[m_burer_e]:monster_baseТак вот, если в конфиге monster_base добавить параметрmonster = trueВот так:[monster_base]:common_ph_friction_params_on_npc_deathactor_hit_effect = effector_monster_hitmonster = trueто тогда таблица IAmAMonster[obj:clsid()] - становится абсолютно ненужной. Вместо нее пишем:function IAmAMonster(section)return ini:line_exist(section, "monster")endВсе. Никаких таблиц. Причем, если сделать секции stalker_base, ammo_base, wpn_base, artefact_base, medkit_base, detector_base, food_base, outfit_base и т.д., и задать там аналогичный параметр, то все эти таблицы также станут ненужными.Скажете - IAmAMonster - не такая уж и большая таблица. А как быть тогда с соляночными таблицами в protected_items? Также с различными списками вamk_mod.anom_recept_komp,news_data.ommunity_name, monster_classes, weapon_classes, различными templates,art_hit.art_hit, scient,death_manager.data_by_communityxrs_armor.excluded_npcsи с многими другими таблицами? Ведь для того, чтобы сделать, чтобы item был protected, достаточно создать секцию[protected_item]protected_item = trueИ прописывать ее в наследование в каждую секцию, которую надо защитить:[bandranen_pda]:identity_immunities, protected_itemИ все - и тогда простая проверкаini:section_exist(section, "protected_item")даст нужный результат) Конечно, в данном случае это не дает оптимизации, просто перенос с одного формата в другой. Но если этот параметр используется более чем 1 раз - то это дает экономию столько раз, сколько он используется. В частности прописывание параметраscient = trueво все броники, перечисленные в art_hit.scient полностью избавляет нас от этой таблицы.А также, если сделать вот такую секцию:[iI_ATTCH]:identity_immunitiesGroupControlSection = spawn_groupdiscovery_dependency =$spawn = "devices\quest_items\bandranen_pda";$prefetch = 32class = II_ATTCHcform = skeletonradius = 1И затем написать вместо:[bandranen_pda]:identity_immunities, protected_itemвот так:[bandranen_pda]:II_ATTCH, protected_itemТо тогда в каждой секции на классе II_ATTCH можно выкинуть все перечисленные выше строки, вынесенные в общую секцию. А это только в оригинале ТЧ более 500 ненужных и дублирующихся строк. А если посмотреть по другим классам, и добавить всё, сделанное в модах на оригинал, то количество вырастет на несколько порядков. И этот весь дубликат висит балластом в памяти всю игру, провоцируя вылеты по нехватке ресурсов. Повторюсь: жаль, что понимание этого ко мне пришло только недавно, уже после релиза ОП-2. Иначе в нем можно было еще очень и очень много чего оптимизировать и упростить) Подобного еще можно написать много, я изложил только то, что первое пришло на ум, но надеюсь суть и принципы изложенного понятны) все остальное оптимизируется по аналогии. Надеюсь этот пост окажется кому-то полезным) Изменено 13 Марта 2015 пользователем proper70 2 2 4 Поделиться этим сообщением Ссылка на сообщение