Конфиги: INI vs XML vs Lua vs Custom-Format

Недавно пришлось, наконец, перенести определения UI из кода в конфиги. Интерфейс изначально делался так, чтобы легко можно было из скрипта создавать/управлять GUI (я думал, что UI будет в итоге описываться в Lua), но несколько месяцев UI задавался путём написания множества нудных строчек С++ кода.

Когда дошло до конкретной реализации, выяснилось, что Lua всё-таки не годится. Использовал старый добрый конфиг (собственного формата).
 

Ниже краткая история, как/почему приходят к собственному формату конфигов.

INI

Под ini подразумеваются файлы вот такого формата:

[starfield]
name       = "blue night"
num_layers = 5   
color_0    =

С .ini всё просто. Буквально. Парсер пишется за час-другой; можно парсить in-place, (почти совсем) без дополнительных выделений памяти -- single-read всего файла в буфер и расстановка там ноликов для окончаний полей/значений.

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

XML

Лет пять назад я был большим фанатом ХМЛ -- с ним можно делать иерархические конфиги, можно даже enforce'ить структуру (через DTD/Scheme). Редакторы всякие прииятные для XML опять же есть (XML Spy как пример самого лучшего).

Однако у XML есть один громадный недостаток, который убивает любые его преимущества насмерть -- читать/писать XML "руками" невероятно неудобно. Т.е. банально открыть в FAR/notepad файлик и быстро что-то глянуть/поправить -- неудобно, а в файлах более-менее большого размера -- просто нереально (когда мне приходится сравнивать два COLLADA-файла -- это тот ещё цирк).

Хуже того, смотреть diff двух XML-файлов -- натуральный pain in the arse, особенно когда нужно сделать дестяток таких сравнений при отсмотре истории изменений файла в Perforce.

В итоге, при каждом удобном случае я категорически советую НЕ использовать XML для конфигов никогда. Never ever. К слову, возможность валидировать XML по схеме я толком и не использовал за последних лет пять. Аминь.

Lua

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

Проблема с Lua (и скриптом для конфига вообще) -- опять таки невозможность нормально описывать иерархические структуры и списки. Т.е. с иерархией проблем нет, со списками есть, вот такое на Lua не напишешь:

    some_config =   
    {
        node = { param=11 },
        node = { param=22 },
    }

Т.е. написать то можно и ошибок как бы нет, но в итоге мы получим один node, а не два (как хотелось). Вдобавок, порядок обхода элементов структур не определён, т.е. вот в таком примере

    data =
    {
        node1 = { param=11 },
        node2 = { param=22 },
        node3 = { param=33 },
    }

    for k,node in pairs(data) do print( node.param) end

все ноды будет перечисляться НЕ в порядке node1,node2,node3 -- поля в табличках Lua сортируются по хешу ключа (имени, в этом примере) --, и хватает ситуаций, когда такая анти-фича весьма критична.

Из приятных фичей Lua-конфигов стоит упомянуть возможность делать любые вычисления по ходу дела (всё-таки Lua -- полноценный язык программирования); можно даже генерить одни конфиги из других и всякое такое.

Свой Формат

В итоге всё пришло к тому, что используется свой формат.
 

Ключевые фичи:

  • С-подобный синтаксис, легко читается и редактируется
  • типизированные поля (строка, число, bool, цвет в формате , дата)
  • поддержка иерархической структуры
  • гарантированный порядок обхода child-node'ов
  • поддержка
  • поддержка
  • поддержка базовых вычислений -- $(eval ) а ля GNU-make, с использованием значений из 'ов


include и define особенно полезны тем, что конфиги и С++ код могут использовать некие общие константы (например, всякие теги для коллижна ландшафта).
 

Достаточно забавно, что до сих пор поддерживается XML-парсер для конфигов, без большей части дополнительных фич конечно.

Выглядит типовой конфиг так:  

"config/entity/_enemy.lc"

Subtype
{
    uid             = "ZombieMale"
    model           = "Zombie'Male1,Zombie'Male2,Zombie'Male3"   
//    additive_pose   = "Zombie'additive-pose-01,Zombie'additive-pose-02,Zombie'additive-pose-03,Zombie'additive-pose-04"
//    additive_pose   = "Zombie'additive-pose-05"

    idle
    {
        weight     = 10
        animation  = "Zombie'idle-1"
        sfx        = "z_idle01_breathe,z_idle02_growl,z_idle03_growl,z_idle04_hiss,z_idle05_hiss"
        sfx_volume = $(BASE_ZOMBIE_IDLE_SFX_VOLUME)
    }
    idle
    {
        weight     = 2
        animation  = "Zombie'idle-2"
        sfx        = "z_idle01_breathe,z_idle02_growl,z_idle03_growl,,z_idle10_growl,z_idle11_breathe_long"
        sfx_volume = $(BASE_ZOMBIE_IDLE_SFX_VOLUME)
    }
    idle
    {
        weight     = 10
        animation  = "Zombie'idle-3"
        sfx        = "z_idle01_breathe,z_idle02_growl,z_idle03_growl,,z_idle10_growl,z_idle11_breathe_long"
        sfx_volume = $(BASE_ZOMBIE_IDLE_SFX_VOLUME)
    }

   
    attack
    {
        weight             = 10
        animation          = "Zombie'attack-1"
        active_phase_begin = 0.277
        active_phase_end   = 0.555
        sfx                = "z_attack1_01,z_attack1_02,z_attack1_03"
        sfx_volume         = $(BASE_ZOMBIE_ATTACK_SFX_VOLUME)
        hit_sfx            = "z_hit01_FleshJuicy,z_hit02_FleshJuicy,z_hit05_dry_punch,z_hit06_dry_punch,z_hit07_rip,z_hit08_rip,z_hit09_rip"
        hit_sfx_volume     = $(BASE_ZOMBIE_HIT_SFX_VOLUME)
    }   
    attack
    {
        weight             = 10
        animation          = "Zombie'attack-2"
        active_phase_begin = $(eval 25/75 )
        active_phase_end   = $(eval 58/75 )
        sfx                = "z_attack2_01,z_attack2_02,z_attack2_03"
        sfx_volume         = $(BASE_ZOMBIE_ATTACK_SFX_VOLUME)
        hit_sfx            = "z_hit04_dry_punch,z_hit05_dry_punch,z_hit06_dry_punch,z_hit07_rip,z_hit08_rip,z_hit09_rip"
        hit_sfx_volume     = $(BASE_ZOMBIE_HIT_SFX_VOLUME)
    }   
    attack
    {
        weight             = 2
        animation          = "Zombie'attack-3"
        active_phase_begin = $(eval 22/75 )
        active_phase_end   = $(eval 34/75 )
        sfx                = "z_attack3_01,z_attack3_02,z_attack3_03"
        sfx_volume         = $(BASE_ZOMBIE_ATTACK_SFX_VOLUME)
        hit_sfx            = "z_hit01_FleshJuicy,z_hit02_FleshJuicy,z_hit06_dry_punch,z_hit07_rip,z_hit08_rip,z_hit09_rip"
        hit_sfx_volume     = $(BASE_ZOMBIE_HIT_SFX_VOLUME)
    }   
    attack
    {
        weight             = 5
        animation          = "Zombie'attack-3b"
        active_phase_begin = $(eval 23/75 )
        active_phase_end   = $(eval 40/75 )
        sfx                = "z_attack3_01,z_attack3_02,z_attack3_03"
        sfx_volume         = $(BASE_ZOMBIE_ATTACK_SFX_VOLUME)
        hit_sfx            = "z_hit03_FleshJuicy,z_hit04_dry_punch,z_hit05_dry_punch,z_hit06_dry_punch,z_hit07_rip,z_hit08_rip,z_hit09_rip"
        hit_sfx_volume     = $(BASE_ZOMBIE_HIT_SFX_VOLUME)
    }
       
    suspend_distance    = 37
}

Последняя правка: чт, 02/05/2013 - 13:28
Submitted by BLK Dragon on

Комментарии

Да, свой формат конечно же очень хорошо. Лучше наверное может быть только редактор ГУИ по типу дельфийского - чтобы просто и легко мышкой создавать интерфейсы. И желательно чтобы сохранял в бинарный формат дабы исключить случайную или преднамеренную порчу этого файла пользователем. Сам я пока нахожусь на этапе поклонения XML Smile - пока устраивает.

Submitted by DekaSoft on
Бинарный формат для конфигов -- худшее что можно придумать; при изменении формата (добавление/удаление полей), старые конфиги немедленно перестают читаться новым .ехе а новые конфиги -- старым .ехе (да ещё, с неплохой вероятностью, с фантастическими глюками в нагрузку, в обоих случаях).

А мешать пользователю править конфиги получается само собой -- в релизе все конфиги складываются в архив и компрессятся, нехай правят на здоровье Smile

Игровой Гуй в тулзе а ля дельфи фигово делать на самом деле, ну если не закладываться на одно единственное разрешение экрана. Тут больше подходит концепция wxWidgets с sizer'ами -- при любых размерах окна гуй выглядит прилично.

Submitted by BLK Dragon on

да согласен, XML как формат файла который надо править крайне не удобен, пока пользуем но хочу переписать на JSON или его аналог - без запятых, ты я смотрю пошёл ещё дальше, и препроцессор сверху ещё навернул Smile , может у тебя ещё и обрабока и директив есть ?

Submitted by Relyer on

Имхо, лучше всего написать отображение того же xml в property grid, и необходимость править конфиги ручками отпадет )

Submitted by 1nq on

Для работы с конфигурациями и настройками для С++ я пользуюсь классом QSettings библиотеки Nokia QT. Его возможности описаны по ссылке. Он поддерживает несколько форматов файлов настроек: нативный (для Windows это реестр) и INI. Любой другой Custom формат просто регистрируется в классе. Это может быть и xml к примеру. На C# я использую стандартный генератор для этих вещей т. е. через Visual Studio настройки проекта.

Submitted by Necro on

А если использовать формат XLS? что может быть удобнее в редактировании.

Submitted by Hale_32bit on
Quote:
1nq писал(а):
Имхо, лучше всего написать отображение того же xml в property grid, и необходимость править конфиги ручками отпадет )
Необходимость смотреть/редактировать конфиги ручками никогда не отпадёт. Писать гламурный редактор на каждую мелкую хреньку никаких сил не хватит. И потом, все почему-то забывают что должна быть возможность быстро и легко сравнить два файла конфига -- например посмотреть что изменилось.

XML и XLS ну совсем не легко и просто сравниваются (я не файлики из трёх строчек, а про настоящие "боевые" конфиги).

Submitted by BLK Dragon on
Quote:
1nq писал(а):
Имхо, лучше всего написать отображение того же xml в property grid, и необходимость править конфиги ручками отпадет )
Тоже кстати очень хороший вариант на мой взгляд. Дешево и сердито. Думаю, мне он больше всего подходит.
Submitted by DekaSoft on
Филосовский вопрос - где заканчиваются конфиги и начинаются скрипты? )

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

Submitted by Victor on
Quote:
Victor писал(а):
Филосовский вопрос - где заканчиваются конфиги и начинаются скрипты? )
В том смысле что если конфиги делать на базе скриптового языка то разделение будет чисто условными.
Разделение очень простое и чёткое на самом деле.

Конфиг -- декларативная штука, описание данных. Скрипт -- императивная штука, он может действия всякие выполнять, по своей логике.

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

</p>

<p>Data = { a=1 b=2 c=44 }</p>

    local mp = App.GetMoonPhase()    <p>if mp ~= 13 then Data.b = Data.a + mp + 7 end</p>

<p>

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

Submitted by BLK Dragon on

GameDev.by