diff --git a/.gitignore b/.gitignore index 606033f3..72fa6d1a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Debug/voxel_engine /screenshots /out +/misc /world /worlds/**/* /settings.toml @@ -41,4 +42,4 @@ appimage-build/ *~ /res/content/* -!/res/content/base \ No newline at end of file +!/res/content/base diff --git a/doc/en/8.Scripting.md b/doc/en/8.Scripting.md index de5e0ca3..e18ab0ad 100644 --- a/doc/en/8.Scripting.md +++ b/doc/en/8.Scripting.md @@ -95,6 +95,26 @@ world.get_seed() -> int Returns world seed. +## *pack* library + +```python +pack.get_folder(packid: str) -> str +``` + +Returns installed content-pack folder + +```python +pack.is_installed(packid: str) -> bool +``` + +Check if the world has specified pack installed + +```python +pack.get_installed() -> array of strings +``` + +Returns all installed content-pack ids + ## *gui* library Library contains ui elements access functions. Library should not be directly used, because script *layouts/layout_name.xml.lua* already has a generated variable **document** (instance of **Document**) @@ -161,6 +181,13 @@ inventory.clone(invid: int) -> int Create inventory copy. Returns the created copy ID. +```python +inventory.move(invA: int, slotA: int, invB: int, slotB: int) +``` + +Move item from slotA of invA to slotB of invB. invA may be the same as invB. +If slotB will be chosen automaticly if argument is not specified. + ## *block* library ```python @@ -337,6 +364,13 @@ hud.close(layoutid: str) Remove an element from the screen + +```python +hud.get_block_inventory() -> int +``` + +Get open block inventory ID or 0 + ## Block events ```lua diff --git a/doc/ru/8.Скриптинг.md b/doc/ru/8.Скриптинг.md index 2b2a7326..82f27159 100644 --- a/doc/ru/8.Скриптинг.md +++ b/doc/ru/8.Скриптинг.md @@ -89,6 +89,26 @@ world.get_seed() -> int Возвращает зерно мира. +## Библиотека pack + +```python +pack.get_folder(packid: str) -> str +``` + +Возвращает путь к папке установленного контент-пака + +```python +pack.is_installed(packid: str) -> bool +``` + +Проверяет наличие контент-пака в мире + +```python +pack.get_installed() -> массив строк +``` + +Возращает id всех установленных в мире контент-паков + ## Библиотека gui Библиотека содержит функции для доступа к свойствам UI элементов. Вместо gui следует использовать объектную обертку, предоставляющую доступ к свойствам через мета-методы __index, __newindex: @@ -155,6 +175,14 @@ inventory.clone(invid: int) -> int Создает копию инвентаря и возвращает id копии. Если копируемого инвентаря не существует, возвращает 0. +```python +inventory.move(invA: int, slotA: int, invB: int, slotB: int) +``` + +Перемещает предмет из slotA инвентаря invA в slotB инвентаря invB. +invA и invB могут указывать на один инвентарь. +slotB будет выбран автоматически, если не указывать явно. + ## Библиотека block ```python @@ -326,6 +354,13 @@ hud.close(layoutid: str) ``` Удаляет элемент с экрана + +```python +hud.get_block_inventory() -> int +``` + +Получить ID инвентаря открытого блока или 0 + ## События блоков ```lua diff --git a/res/content/base/texts/en_US.txt b/res/content/base/texts/en_US.txt deleted file mode 100644 index 05c8d3dc..00000000 --- a/res/content/base/texts/en_US.txt +++ /dev/null @@ -1,22 +0,0 @@ -bazalt=Bazalt -blue_lamp=Blue Lamp -brick=Brick -dirt=Dirt -flower=Flower -glass=Glass -grass_block=Ground -grass=Tall Grass -green_lamp=Green Lamp -lamp=Lamp -leaves=Leaves -lightbulb=Bulb -metal=Metal -pane=Pane -pipe=Pipe -planks=Planks -red_lamp=Red Lamp -rust=Rust -sand=Sand -stone=Stone -water=Water -wood=Wood \ No newline at end of file diff --git a/res/content/base/texts/ru_RU.txt b/res/content/base/texts/ru_RU.txt index 148c0a5a..b72d907e 100644 --- a/res/content/base/texts/ru_RU.txt +++ b/res/content/base/texts/ru_RU.txt @@ -1,22 +1,22 @@ -bazalt=Базальт -blue_lamp=Синяя Лампа -brick=Кирпич -dirt=Земля -flower=Цветок -glass=Стекло -grass_block=Дёрн -grass=Высокая Трава -green_lamp=Зелёная Лампа -lamp=Лампа -leaves=Листва -lightbulb=Лампочка -metal=Металл -pane=Панель -pipe=Труба -planks=Доски -red_lamp=Красная Лампа -rust=Ржавчина -sand=Песок -stone=Камень -water=Вода -wood=Бревно \ No newline at end of file +bazalt=базальт +blue lamp=синяя лампа +brick=кирпич +dirt=земля +flower=цветок +glass=стекло +grass block=дёрн +tall grass=высокая трава +green lamp=зелёная лампа +lamp=лампа +leaves=листва +light bulb=лампочка +metal=металл +pane=панель +pipe=труба +planks=доски +red lamp=красная лампа +rust=ржавчина +sand=песок +stone=камень +water=вода +wood=бревно diff --git a/res/layouts/inventory.xml.lua b/res/layouts/inventory.xml.lua index bc3fc5d2..c2280f15 100644 --- a/res/layouts/inventory.xml.lua +++ b/res/layouts/inventory.xml.lua @@ -1,3 +1,8 @@ function inventory_share_func(invid, slotid) - inventory.set(invid, slotid, 0, 0) + local blockinv = hud.get_block_inventory() + if blockinv ~= 0 then + inventory.move(invid, slotid, blockinv) + else + inventory.set(invid, slotid, 0, 0) + end end diff --git a/res/layouts/pages/404.xml b/res/layouts/pages/404.xml new file mode 100644 index 00000000..c53262bc --- /dev/null +++ b/res/layouts/pages/404.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/pages/content.xml b/res/layouts/pages/content.xml new file mode 100644 index 00000000..c4ff39e2 --- /dev/null +++ b/res/layouts/pages/content.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua new file mode 100644 index 00000000..2a21d57d --- /dev/null +++ b/res/layouts/pages/content.xml.lua @@ -0,0 +1,131 @@ +function on_open(params) + if params then + mode = params.mode + end + refresh() +end + +add_packs = {} +rem_packs = {} + +function apply() + core.reconfig_packs(add_packs, rem_packs) + if mode ~= "world" then + menu:back() + end +end + +function refresh_changes() + document.apply_btn.enabled = (#add_packs>0) or (#rem_packs>0) +end + +function move_pack(id) + -- cancel pack addition + if table.has(add_packs, id) then + document["pack_"..id]:move_into(document.packs_add) + table.remove_value(add_packs, id) + -- cancel pack removal + elseif table.has(rem_packs, id) then + document["pack_"..id]:move_into(document.packs_cur) + table.remove_value(rem_packs, id) + -- add pack + elseif table.has(packs_installed, id) then + document["pack_"..id]:move_into(document.packs_add) + table.insert(rem_packs, id) + -- remove pack + else + document["pack_"..id]:move_into(document.packs_cur) + table.insert(add_packs, id) + end + refresh_changes() +end + +function place_pack(panel, packinfo, callback) + if packinfo.error then + callback = nil + end + if packinfo.has_indices then + packinfo.id_verbose = packinfo.id.."*" + else + packinfo.id_verbose = packinfo.id + end + packinfo.callback = callback + panel:add(gui.template("pack", packinfo)) + if not callback then + document["pack_"..packinfo.id].enabled = false + end +end + +function check_dependencies(packinfo) + if packinfo.dependencies == nil then + return + end + for i,dep in ipairs(packinfo.dependencies) do + local depid = dep:sub(2,-1) + if dep:sub(1,1) == '!' then + if not table.has(packs_all, depid) then + return string.format( + "%s (%s)", gui.str("error.dependency-not-found"), depid + ) + end + if table.has(packs_installed, packinfo.id) then + table.insert(required, depid) + end + end + end + return +end + +function refresh() + packs_installed = pack.get_installed() + packs_available = pack.get_available() + base_packs = pack.get_base_packs() + packs_all = {unpack(packs_installed)} + required = {} + for i,k in ipairs(packs_available) do + table.insert(packs_all, k) + end + + local packs_cur = document.packs_cur + local packs_add = document.packs_add + packs_cur:clear() + packs_add:clear() + + for i,id in ipairs(packs_installed) do + local packinfo = pack.get_info(id) + packinfo.index = i + callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil + packinfo.error = check_dependencies(packinfo) + place_pack(packs_cur, packinfo, callback) + end + + for i,id in ipairs(packs_available) do + local packinfo = pack.get_info(id) + packinfo.index = i + callback = string.format('move_pack("%s")', id) + packinfo.error = check_dependencies(packinfo) + place_pack(packs_add, packinfo, callback) + end + + for i,id in ipairs(packs_installed) do + if table.has(required, id) then + document["pack_"..id].enabled = false + end + end + + apply_movements(packs_cur, packs_add) + refresh_changes() +end + +function apply_movements(packs_cur, packs_add) + for i,id in ipairs(packs_installed) do + if table.has(rem_packs, id) then + document["pack_"..id]:move_into(packs_add) + end + end + for i,id in ipairs(packs_available) do + if table.has(add_packs, id) then + document["pack_"..id]:move_into(packs_cur) + end + end +end diff --git a/res/layouts/pages/generators.xml b/res/layouts/pages/generators.xml new file mode 100644 index 00000000..a67d06b8 --- /dev/null +++ b/res/layouts/pages/generators.xml @@ -0,0 +1,3 @@ + + + diff --git a/res/layouts/pages/generators.xml.lua b/res/layouts/pages/generators.xml.lua new file mode 100644 index 00000000..dd9b6743 --- /dev/null +++ b/res/layouts/pages/generators.xml.lua @@ -0,0 +1,16 @@ +settings = session.get_entry('new_world') + +function on_open() + local names = core.get_generators() + table.sort(names) + + local panel = document.root + for _,k in ipairs(names) do + panel:add(gui.template("generator", { + callback=string.format("settings.generator=%q menu:back()", k), + id=k, + name=settings.generator_name(k) + })) + end + panel:add("") +end diff --git a/res/layouts/pages/languages.xml b/res/layouts/pages/languages.xml new file mode 100644 index 00000000..38722bed --- /dev/null +++ b/res/layouts/pages/languages.xml @@ -0,0 +1,3 @@ + + + diff --git a/res/layouts/pages/languages.xml.lua b/res/layouts/pages/languages.xml.lua new file mode 100644 index 00000000..a064c68c --- /dev/null +++ b/res/layouts/pages/languages.xml.lua @@ -0,0 +1,19 @@ +function on_open() + local locales = gui.get_locales_info() + local invlocales = {} + local names = {} + for k, v in pairs(locales) do + table.insert(names, v.name) + invlocales[v.name] = k + end + table.sort(names) + + local panel = document.root + for _,k in ipairs(names) do + panel:add(string.format( + "", + string.format("core.set_setting('ui.language', %q) menu:back()", invlocales[k]), k + )) + end + panel:add("") +end diff --git a/res/layouts/pages/main.xml b/res/layouts/pages/main.xml new file mode 100644 index 00000000..3ff1c23f --- /dev/null +++ b/res/layouts/pages/main.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/layouts/pages/main.xml.lua b/res/layouts/pages/main.xml.lua new file mode 100644 index 00000000..37c348a9 --- /dev/null +++ b/res/layouts/pages/main.xml.lua @@ -0,0 +1,6 @@ +function on_open() + local worlds = world.get_list() + for _, info in ipairs(worlds) do + document.worlds:add(gui.template("world", info)) + end +end diff --git a/res/layouts/pages/new_world.xml b/res/layouts/pages/new_world.xml new file mode 100644 index 00000000..9042ea57 --- /dev/null +++ b/res/layouts/pages/new_world.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/res/layouts/pages/new_world.xml.lua b/res/layouts/pages/new_world.xml.lua new file mode 100644 index 00000000..e92d5ae0 --- /dev/null +++ b/res/layouts/pages/new_world.xml.lua @@ -0,0 +1,47 @@ +settings = session.get_entry('new_world') + +function world_name_validator(name) + return name:match("^[%w-\\.\\ ]+$") ~= nil and not world.exists(name) +end + +function save_state() + settings.name = document.name_box.text + settings.seed = document.seed_box.text +end + +function settings.generator_name(id) + local prefix, name = parse_path(id) + if prefix == "core" then + return gui.str(name, "world.generators") + else + return id + end +end + +function create_world() + if not document.name_box.valid then + return + end + local name = document.name_box.text + local seed = document.seed_box.text + local generator = settings.generator + session.reset_entry('new_world') + core.new_world(name, seed, generator) +end + +function on_open() + document.content_btn.text = string.format( + "%s [%s]", gui.str("Content", "menu"), #pack.get_installed() + ) + if settings.generator == nil then + settings.generator = core.get_default_generator() + end + document.generator_btn.text = string.format( + "%s: %s", + gui.str("World generator", "world"), + settings.generator_name(settings.generator) + ) + document.name_box.text = settings.name or '' + document.seed_box.text = settings.seed or '' + document.seed_box.placeholder = tostring(math.random()):sub(3) +end diff --git a/res/layouts/pages/pause.xml b/res/layouts/pages/pause.xml new file mode 100644 index 00000000..63d827e1 --- /dev/null +++ b/res/layouts/pages/pause.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml new file mode 100644 index 00000000..5e0ea285 --- /dev/null +++ b/res/layouts/pages/settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/res/layouts/pages/settings.xml.lua b/res/layouts/pages/settings.xml.lua new file mode 100644 index 00000000..f21f25c3 --- /dev/null +++ b/res/layouts/pages/settings.xml.lua @@ -0,0 +1,15 @@ +function on_open() + document.langs_btn.text = string.format( + "%s: %s", gui.str("Language", "settings"), + gui.get_locales_info()[core.get_setting("ui.language")].name + ) + set_page("s_gfx", "settings_graphics") +end + +function set_page(btn, page) + document.s_aud.enabled = true + document.s_gfx.enabled = true + document.s_ctl.enabled = true + document[btn].enabled = false + document.menu.page = page +end diff --git a/res/layouts/pages/settings_audio.xml b/res/layouts/pages/settings_audio.xml new file mode 100644 index 00000000..2b3c1818 --- /dev/null +++ b/res/layouts/pages/settings_audio.xml @@ -0,0 +1,2 @@ + + diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua new file mode 100644 index 00000000..9b29bd52 --- /dev/null +++ b/res/layouts/pages/settings_audio.xml.lua @@ -0,0 +1,33 @@ +function create_setting(id, name, step, postfix) + local info = core.get_setting_info(id) + postfix = postfix or "" + document.root:add(gui.template("track_setting", { + id=id, + name=gui.str(name, "settings"), + value=core.get_setting(id), + min=info.min, + max=info.max, + step=step, + postfix=postfix + })) + update_setting(core.get_setting(id), id, name, postfix) +end + +function update_setting(x, id, name, postfix) + core.set_setting(id, x) + -- updating label + document[id..".L"].text = string.format( + "%s: %s%s", + gui.str(name, "settings"), + core.str_setting(id), + postfix + ) +end + +function on_open() + create_setting("audio.volume-master", "Master Volume", 0.01) + create_setting("audio.volume-regular", "Regular Sounds", 0.01) + create_setting("audio.volume-ui", "UI Sounds", 0.01) + create_setting("audio.volume-ambient", "Ambient", 0.01) + create_setting("audio.volume-music", "Music", 0.01) +end diff --git a/res/layouts/pages/settings_controls.xml b/res/layouts/pages/settings_controls.xml new file mode 100644 index 00000000..d61e3e5b --- /dev/null +++ b/res/layouts/pages/settings_controls.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/res/layouts/pages/settings_controls.xml.lua b/res/layouts/pages/settings_controls.xml.lua new file mode 100644 index 00000000..a66e17e3 --- /dev/null +++ b/res/layouts/pages/settings_controls.xml.lua @@ -0,0 +1,25 @@ +function refresh_sensitivity() + document.sensitivity_label.text = string.format( + "%s: %s", + gui.str("Mouse Sensitivity", "settings"), + core.str_setting("camera.sensitivity") + ) +end + +function change_sensitivity(val) + core.set_setting("camera.sensitivity", val) + refresh_sensitivity() +end + +function on_open() + document.sensitivity_track.value = core.get_setting("camera.sensitivity") + refresh_sensitivity() + + local panel = document.bindings_panel + local bindings = core.get_bindings() + for i,name in ipairs(bindings) do + panel:add(gui.template("binding", { + id=name, name=gui.str(name) + })) + end +end diff --git a/res/layouts/pages/settings_graphics.xml b/res/layouts/pages/settings_graphics.xml new file mode 100644 index 00000000..2aa3aeca --- /dev/null +++ b/res/layouts/pages/settings_graphics.xml @@ -0,0 +1,2 @@ + + diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua new file mode 100644 index 00000000..835b3746 --- /dev/null +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -0,0 +1,44 @@ +function create_setting(id, name, step, postfix) + local info = core.get_setting_info(id) + postfix = postfix or "" + document.root:add(gui.template("track_setting", { + id=id, + name=gui.str(name, "settings"), + value=core.get_setting(id), + min=info.min, + max=info.max, + step=step, + postfix=postfix + })) + update_setting(core.get_setting(id), id, name, postfix) +end + +function update_setting(x, id, name, postfix) + core.set_setting(id, x) + -- updating label + document[id..".L"].text = string.format( + "%s: %s%s", + gui.str(name, "settings"), + core.str_setting(id), + postfix + ) +end + +function create_checkbox(id, name) + document.root:add(string.format( + "%s", + id, core.str_setting(id), gui.str(name, "settings") + )) +end + +function on_open() + create_setting("chunks.load-distance", "Load Distance", 1) + create_setting("chunks.load-speed", "Load Speed", 1) + create_setting("graphics.fog-curve", "Fog Curve", 0.1) + create_setting("graphics.gamma", "Gamma", 0.05) + create_setting("camera.fov", "FOV", 1, "°") + create_checkbox("display.fullscreen", "Fullscreen") + create_checkbox("display.vsync", "V-Sync") + create_checkbox("graphics.backlight", "Backlight") + create_checkbox("camera.shaking", "Camera Shaking") +end diff --git a/res/layouts/process.xml b/res/layouts/process.xml new file mode 100644 index 00000000..ccf631d8 --- /dev/null +++ b/res/layouts/process.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/process.xml.lua b/res/layouts/process.xml.lua new file mode 100644 index 00000000..44e640e8 --- /dev/null +++ b/res/layouts/process.xml.lua @@ -0,0 +1,10 @@ +function on_progress(done, total) + local progress = done / total + document.progress_label.text = string.format( + "%s/%s (%s%%)", done, total, math.floor(progress*100) + ) +end + +function on_open(title) + document.title_label.text = title +end diff --git a/res/layouts/reports/missing_content.xml b/res/layouts/reports/missing_content.xml new file mode 100644 index 00000000..cd8cacb4 --- /dev/null +++ b/res/layouts/reports/missing_content.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/layouts/reports/missing_content.xml.lua b/res/layouts/reports/missing_content.xml.lua new file mode 100644 index 00000000..8ea78a1c --- /dev/null +++ b/res/layouts/reports/missing_content.xml.lua @@ -0,0 +1,5 @@ +function on_open(report) + for i, entry in ipairs(report.content) do + document.content_panel:add(gui.template("content_entry", entry)) + end +end diff --git a/res/layouts/templates/binding.xml b/res/layouts/templates/binding.xml new file mode 100644 index 00000000..643bd5aa --- /dev/null +++ b/res/layouts/templates/binding.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/templates/content_entry.xml b/res/layouts/templates/content_entry.xml new file mode 100644 index 00000000..cfb9ce68 --- /dev/null +++ b/res/layouts/templates/content_entry.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/templates/generator.xml b/res/layouts/templates/generator.xml new file mode 100644 index 00000000..51120738 --- /dev/null +++ b/res/layouts/templates/generator.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/layouts/templates/pack.xml b/res/layouts/templates/pack.xml new file mode 100644 index 00000000..5eb2f74c --- /dev/null +++ b/res/layouts/templates/pack.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/res/layouts/templates/track_setting.xml b/res/layouts/templates/track_setting.xml new file mode 100644 index 00000000..994e5bb3 --- /dev/null +++ b/res/layouts/templates/track_setting.xml @@ -0,0 +1,6 @@ + + + + diff --git a/res/layouts/templates/world.xml b/res/layouts/templates/world.xml new file mode 100644 index 00000000..c20223fc --- /dev/null +++ b/res/layouts/templates/world.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/res/preload.json b/res/preload.json index bc5bc2ac..63c2d238 100644 --- a/res/preload.json +++ b/res/preload.json @@ -11,4 +11,3 @@ "gui/crosshair" ] } - diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index d8397f57..dcc7f382 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -131,7 +131,9 @@ events = { } function events.on(event, func) - events.handlers[event] = events.handlers[event] or {} + -- why an array? length is always = 1 + -- FIXME: temporary fixed + events.handlers[event] = {} -- events.handlers[event] or {} table.insert(events.handlers[event], func) end @@ -175,11 +177,105 @@ Document = {} function Document.new(docname) return setmetatable({name=docname}, { __index=function(self, k) - return Element.new(self.name, k) + local elem = Element.new(self.name, k) + rawset(self, k, elem) + return elem end }) end +_GUI_ROOT = Document.new("core:root") +_MENU = _GUI_ROOT.menu +menu = _MENU + +local __post_runnables = {} + +function __process_post_runnables() + if #__post_runnables then + for _, func in ipairs(__post_runnables) do + func() + end + __post_runnables = {} + end +end + +function time.post_runnable(runnable) + table.insert(__post_runnables, runnable) +end + +function gui.template(name, params) + local text = file.read(file.find("layouts/templates/"..name..".xml")) + for k,v in pairs(params) do + local arg = tostring(v):gsub("'", "\\'"):gsub('"', '\\"') + text = text:gsub("(%%{"..k.."})", arg) + end + text = text:gsub("if%s*=%s*'%%{%w+}'", "if=''") + text = text:gsub("if%s*=%s*\"%%{%w+}\"", "if=\"\"") + -- remove unsolved properties: attr='%{var}' + text = text:gsub("%w+%s*=%s*'%%{%w+}'%s?", "") + text = text:gsub("%w+%s*=%s*\"%%{%w+}\"%s?", "") + return text +end + +session = { + entries={} +} + +function session.get_entry(name) + local entry = session.entries[name] + if entry == nil then + entry = {} + session.entries[name] = entry + end + return entry +end + +function session.reset_entry(name) + session.entries[name] = nil +end + +function timeit(func, ...) + local tm = time.uptime() + func(...) + print("[time mcs]", (time.uptime()-tm) * 1000000) +end + +function table.has(t, x) + for i,v in ipairs(t) do + if v == x then + return true + end + end + return false +end + +function table.index(t, x) + for i,v in ipairs(t) do + if v == x then + return i + end + end + return -1 +end + +function table.remove_value(t, x) + local index = table.index(t, x) + if index ~= -1 then + table.remove(t, index) + end +end + +function table.tostring(t) + local s = '[' + for i,v in ipairs(t) do + s = s..tostring(v) + if i < #t then + s = s..', ' + end + end + return s..']' +end + -- Deprecated functions block_index = block.index block_name = block.name diff --git a/res/texts/de_DE.txt b/res/texts/de_DE.txt index ce6b21ec..0251f395 100644 --- a/res/texts/de_DE.txt +++ b/res/texts/de_DE.txt @@ -13,44 +13,44 @@ error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden pack.remove-confirm=Alle vom Paket bereitgestellten Inhalte (dauerhaft) aus der Welt löschen? # Меню +menu.Apply=Anwenden +menu.Audio=Klang +menu.Back to Main Menu=Zurück zum Menü +menu.Content Error=Inhaltsfehler +menu.Content=Inhalt +menu.Continue=Weitermachen +menu.Controls=Kontrolle +menu.Create World=Erschaffe eine Welt +menu.missing-content=Fehlender Inhalt! menu.New World=Neue Welt menu.Quit=Ausfahrt -menu.Continue=Weitermachen menu.Save and Quit to Menu=Speichern und zum Menü zurückkehren -menu.missing-content=Fehlender Inhalt! -menu.Content Error=Inhaltsfehler -menu.Controls=Kontrolle -menu.Back to Main Menu=Zurück zum Menü menu.Settings=Einstellungen -menu.Content=Inhalt -menu.Audio=Klang + world.Seed=Mais world.Name=Name - world.World generator=Weltgenerator world.generators.default=Normal world.generators.flat=Wohnung -menu.Create World=Erschaffe eine Welt - +world.Create World=Welt Erschaffen world.convert-request=Es gibt Änderungen in den Indizes! Die Welt bekehren? world.delete-confirm=Die Welt dauerhaft löschen? # Настройки +settings.Ambient=Hintergrund +settings.Backlight=Hintergrundbeleuchtung +settings.Camera Shaking=Wackelnde Kamera +settings.Fog Curve=Nebelkurve +settings.FOV=Sichtlinie +settings.Language=Sprache settings.Load Distance=Ladeentfernung settings.Load Speed=Download-Geschwindigkeit -settings.Fog Curve=Nebelkurve -settings.Backlight=Hintergrundbeleuchtung -settings.V-Sync=Vertikale Synchronisation -settings.Camera Shaking=Wackelnde Kamera settings.Master Volume=Allgemeine Lautstärke +settings.Mouse Sensitivity=Maus-Empfindlichkeit +settings.Music=Musik settings.Regular Sounds=Normale Geräusche settings.UI Sounds=Interface-Sounds -settings.Ambient=Hintergrund -settings.Music=Musik - -settings.FOV=Sichtlinie -settings.Mouse Sensitivity=Maus-Empfindlichkeit -settings.Language=Sprache +settings.V-Sync=Vertikale Synchronisation # Управление movement.forward=Vorwärts diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 99b97604..94d6774b 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -1,7 +1,7 @@ # Menu menu.missing-content=Missing Content! world.convert-request=Content indices have changed! Convert world files? -pack.remove-confirm=Do you want to erase all pack content from the world forever? +pack.remove-confirm=Do you want to erase all pack(s) content from the world forever? error.pack-not-found=Could not to find pack error.dependency-not-found=Dependency pack is not found world.delete-confirm=Do you want to delete world forever? @@ -24,4 +24,4 @@ player.build=Place Block player.flight=Flight player.noclip=No-clip camera.zoom=Zoom -camera.mode=Switch Camera Mode \ No newline at end of file +camera.mode=Switch Camera Mode diff --git a/res/texts/pl_PL.txt b/res/texts/pl_PL.txt index aebdab1c..39f1a1a7 100644 --- a/res/texts/pl_PL.txt +++ b/res/texts/pl_PL.txt @@ -5,38 +5,53 @@ Ok=OK Cancel=Anulowanie Back=Powrót Continue=Kontynuacja +Add=Dodać +Converting world...=Konwersja świata w toku... error.pack-not-found=Nie udało się znaleźć pakietu # Menu -menu.New World=Nowy Świat -menu.Quit=Wyjście -menu.Continue=Kontynuacja -menu.Save and Quit to Menu=Zapisz i wyjdź do menu -menu.missing-content=Brak treści! -menu.Controls=Zarządzanie +menu.Apply=Zastosować +menu.Audio=Dźwięki menu.Back to Main Menu=Powrót do menu -menu.Settings=Ustawienia menu.Content=Treść +menu.Continue=Kontynuacja +menu.Controls=Zarządzanie +menu.Graphics=Grafika +menu.Create World=Stwórz świat +menu.missing-content=Brakująca treść! +menu.New World=Nowy Świat +menu.Page not found=Strona nie znaleziona +menu.Quit=Wyjście +menu.Save and Quit to Menu=Zapisz i wyjdź do menu +menu.Settings=Ustawienia + world.Seed=Ziarno world.Name=Nazwa -menu.Create World=Stwórz świat - world.World generator=Generator światła world.generators.default=Ekstremalne world.generators.flat=Mieszkanie +world.Create World=Stwórz świat world.convert-request=Szykują się zmiany w indeksach! Przekształcić świat? +world.delete-confirm=Trwale usunąć świat? # Ustawienia +settings.Ambient=Tło +settings.Backlight=Podświetlenie +settings.Camera Shaking=Drżąca kamera +settings.Fog Curve=Krzywa mgły +settings.FOV=Wzrok +settings.Fullscreen=Pełny ekran +settings.Gamma=Gamma +settings.Language=Język settings.Load Distance=Odległość ładowania settings.Load Speed=Prędkość pobierania -settings.Fog Curve=Krzywa mgły -settings.Backlight=Podświetlenie -settings.V-Sync=Synchronizacja pionowa - -settings.FOV=Wzrok +settings.Master Volume=Głośność główna settings.Mouse Sensitivity=Wrażliwość myszy -settings.Language=Język +settings.Music=Muzyka +settings.Regular Sounds=Normalne dźwięki +settings.UI Sounds=Dźwięki interfejsu +settings.V-Sync=Synchronizacja pionowa # Zarządzanie movement.forward=Naprzód diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 25278d13..04ee4e69 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -10,47 +10,50 @@ Converting world...=Выполняется конвертация мира... error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена -pack.remove-confirm=Удалить весь поставляемый паком контент из мира (безвозвратно)? +pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? # Меню -menu.New World=Новый Мир -menu.Quit=Выход -menu.Continue=Продолжить -menu.Save and Quit to Menu=Сохранить и Выйти в Меню -menu.missing-content=Отсутствует Контент! -menu.Content Error=Ошибка Контента -menu.Controls=Управление -menu.Back to Main Menu=Вернуться в Меню -menu.Settings=Настройки -menu.Content=Контент +menu.Apply=Применить menu.Audio=Звук +menu.Back to Main Menu=Вернуться в Меню +menu.Content Error=Ошибка Контента +menu.Content=Контент +menu.Continue=Продолжить +menu.Controls=Управление +menu.Graphics=Графика +menu.missing-content=Отсутствует Контент! +menu.New World=Новый Мир +menu.Page not found=Страница не найдена +menu.Quit=Выход +menu.Save and Quit to Menu=Сохранить и Выйти в Меню +menu.Settings=Настройки + world.Seed=Зерно world.Name=Название - world.World generator=Генератор мира world.generators.default=Обычный world.generators.flat=Плоский -menu.Create World=Создать Мир - +world.Create World=Создать Мир world.convert-request=Есть изменения в индексах! Конвертировать мир? world.delete-confirm=Удалить мир безвозвратно? # Настройки +settings.Ambient=Фон +settings.Backlight=Подсветка +settings.Camera Shaking=Тряска Камеры +settings.Fog Curve=Кривая Тумана +settings.FOV=Поле Зрения +settings.Fullscreen=Полный экран +settings.Gamma=Гамма +settings.Language=Язык settings.Load Distance=Дистанция Загрузки settings.Load Speed=Скорость Загрузки -settings.Fog Curve=Кривая Тумана -settings.Backlight=Подсветка -settings.V-Sync=Вертикальная Синхронизация -settings.Camera Shaking=Тряска Камеры settings.Master Volume=Общая Громкость +settings.Mouse Sensitivity=Чувствительность Мыши +settings.Music=Музыка settings.Regular Sounds=Обычные Звуки settings.UI Sounds=Звуки Интерфейса -settings.Ambient=Фон -settings.Music=Музыка - -settings.FOV=Поле Зрения -settings.Mouse Sensitivity=Чувствительность Мыши -settings.Language=Язык +settings.V-Sync=Вертикальная Синхронизация # Управление movement.forward=Вперёд @@ -67,4 +70,4 @@ player.attack=Атаковать / Сломать player.build=Поставить Блок player.flight=Полёт camera.zoom=Приближение -camera.mode=Сменить Режим Камеры \ No newline at end of file +camera.mode=Сменить Режим Камеры diff --git a/res/texts/uk_UA.txt b/res/texts/uk_UA.txt index 4030d650..175c172f 100644 --- a/res/texts/uk_UA.txt +++ b/res/texts/uk_UA.txt @@ -5,19 +5,26 @@ Ok=Гаразд Cancel=Скасувати Back=Назад Continue=Продовжити +Add=Додати +Converting world...=Виконується конвертація світу... error.pack-not-found=Не вдалося знайти пакет # Меню -menu.New World=Новий Світ -menu.Quit=Вихід -menu.Continue=Продовжити -menu.Save and Quit to Menu=Зберегти і Вийти в Меню -menu.missing-content=Відсутній Контент! -menu.Controls=Керування +menu.Apply=Застосувати +menu.Audio=Звук menu.Back to Main Menu=Повернутися до Меню -menu.Settings=Налаштування +menu.Content Error=Помилка Контенту menu.Content=Контент +menu.Continue=Продовжити +menu.Controls=Керування +menu.Graphics=Графіка +menu.missing-content=Відсутній Контент! +menu.New World=Новий Світ +menu.Page not found=Сторінка не знайдена +menu.Quit=Вихід +menu.Save and Quit to Menu=Зберегти і Вийти в Меню +menu.Settings=Налаштування world.Seed=Зерно world.Name=Назва menu.Create World=Створити Світ @@ -28,15 +35,22 @@ world.generators.flat=Плоскі world.convert-request=Є зміни в індексах! Конвертувати світ? # Налаштування +settings.Ambient=Фон +settings.Backlight=Підсвічування +settings.Camera Shaking=Тряска Камери +settings.Fog Curve=Крива Туману +settings.FOV=Поле зору +settings.Fullscreen=Повний екран +settings.Gamma=Гамма +settings.Language=Мова settings.Load Distance=Дистанція Завантаження settings.Load Speed=Швидкість Завантаження -settings.Fog Curve=Крива Туману -settings.Backlight=Підсвічування -settings.V-Sync=Вертикальна Синхронізація - -settings.FOV=Поле зору +settings.Master Volume=Загальна Гучність settings.Mouse Sensitivity=Чутливість Миші -settings.Language=Мова +settings.Music=Музика +settings.Regular Sounds=Звичайні Звуки +settings.UI Sounds=Звуки інтерфейсу +settings.V-Sync=Вертикальна Синхронізація # Керування movement.forward=Уперед diff --git a/res/textures/gui/circle.png b/res/textures/gui/circle.png new file mode 100644 index 00000000..b82a72ee Binary files /dev/null and b/res/textures/gui/circle.png differ diff --git a/res/textures/gui/cross.png b/res/textures/gui/cross.png index 2656b8bc..021a5ef1 100644 Binary files a/res/textures/gui/cross.png and b/res/textures/gui/cross.png differ diff --git a/res/textures/gui/menubg.png b/res/textures/gui/menubg.png index e3342a02..54135f36 100644 Binary files a/res/textures/gui/menubg.png and b/res/textures/gui/menubg.png differ diff --git a/res/textures/gui/no_icon.png b/res/textures/gui/no_icon.png index 3ad417bf..796058f6 100644 Binary files a/res/textures/gui/no_icon.png and b/res/textures/gui/no_icon.png differ diff --git a/res/textures/gui/no_world_icon.png b/res/textures/gui/no_world_icon.png new file mode 100644 index 00000000..afa28095 Binary files /dev/null and b/res/textures/gui/no_world_icon.png differ diff --git a/res/textures/gui/refresh.png b/res/textures/gui/refresh.png new file mode 100644 index 00000000..ffbbb387 Binary files /dev/null and b/res/textures/gui/refresh.png differ diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index 6dabd8c6..d1c93360 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -1,112 +1,87 @@ -#include "Assets.h" +#include "Assets.hpp" -#include "../audio/audio.h" -#include "../graphics/Texture.h" -#include "../graphics/Shader.h" -#include "../graphics/Atlas.h" -#include "../graphics/Font.h" -#include "../frontend/UiDocument.h" -#include "../logic/scripting/scripting.h" +#include "../audio/audio.hpp" +#include "../graphics/core/Texture.hpp" +#include "../graphics/core/Shader.hpp" +#include "../graphics/core/Atlas.hpp" +#include "../graphics/core/Font.hpp" +#include "../frontend/UiDocument.hpp" +#include "../logic/scripting/scripting.hpp" Assets::~Assets() { } Texture* Assets::getTexture(std::string name) const { - auto found = textures.find(name); - if (found == textures.end()) - return nullptr; - return found->second.get(); + auto found = textures.find(name); + if (found == textures.end()) + return nullptr; + return found->second.get(); } void Assets::store(Texture* texture, std::string name){ - textures.emplace(name, texture); + textures.emplace(name, texture); } Shader* Assets::getShader(std::string name) const{ - auto found = shaders.find(name); - if (found == shaders.end()) - return nullptr; - return found->second.get(); + auto found = shaders.find(name); + if (found == shaders.end()) + return nullptr; + return found->second.get(); } void Assets::store(Shader* shader, std::string name){ - shaders.emplace(name, shader); + shaders.emplace(name, shader); } Font* Assets::getFont(std::string name) const { - auto found = fonts.find(name); - if (found == fonts.end()) - return nullptr; - return found->second.get(); + auto found = fonts.find(name); + if (found == fonts.end()) + return nullptr; + return found->second.get(); } void Assets::store(Font* font, std::string name){ - fonts.emplace(name, font); + fonts.emplace(name, font); } Atlas* Assets::getAtlas(std::string name) const { - auto found = atlases.find(name); - if (found == atlases.end()) - return nullptr; - return found->second.get(); + auto found = atlases.find(name); + if (found == atlases.end()) + return nullptr; + return found->second.get(); } void Assets::store(Atlas* atlas, std::string name){ - atlases.emplace(name, atlas); + atlases.emplace(name, atlas); } audio::Sound* Assets::getSound(std::string name) const { - auto found = sounds.find(name); - if (found == sounds.end()) - return nullptr; - return found->second.get(); + auto found = sounds.find(name); + if (found == sounds.end()) + return nullptr; + return found->second.get(); } void Assets::store(audio::Sound* sound, std::string name) { - sounds.emplace(name, sound); + sounds.emplace(name, sound); } const std::vector& Assets::getAnimations() { - return animations; + return animations; } void Assets::store(const TextureAnimation& animation) { - animations.emplace_back(animation); + animations.emplace_back(animation); } UiDocument* Assets::getLayout(std::string name) const { - auto found = layouts.find(name); - if (found == layouts.end()) - return nullptr; - return found->second.get(); + auto found = layouts.find(name); + if (found == layouts.end()) + return nullptr; + return found->second.get(); } void Assets::store(UiDocument* layout, std::string name) { - layouts.emplace(name, layout); -} - -void Assets::extend(const Assets& assets) { - for (auto entry : assets.textures) { - textures[entry.first] = entry.second; - } - for (auto entry : assets.shaders) { - shaders[entry.first] = entry.second; - } - for (auto entry : assets.fonts) { - fonts[entry.first] = entry.second; - } - for (auto entry : assets.atlases) { - atlases[entry.first] = entry.second; - } - for (auto entry : assets.layouts) { - layouts[entry.first] = entry.second; - } - for (auto entry : assets.sounds) { - sounds[entry.first] = entry.second; - } - animations.clear(); - for (auto entry : assets.animations) { - animations.emplace_back(entry); - } + layouts[name] = std::shared_ptr(layout); } diff --git a/src/assets/Assets.h b/src/assets/Assets.h deleted file mode 100644 index f085ea7e..00000000 --- a/src/assets/Assets.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef ASSETS_ASSETS_H_ -#define ASSETS_ASSETS_H_ - -#include "../graphics/TextureAnimation.h" - -#include -#include -#include -#include - -class Texture; -class Shader; -class Font; -class Atlas; -class UiDocument; - -namespace audio { - class Sound; -} - -class Assets { - std::unordered_map> textures; - std::unordered_map> shaders; - std::unordered_map> fonts; - std::unordered_map> atlases; - std::unordered_map> layouts; - std::unordered_map> sounds; - std::vector animations; -public: - ~Assets(); - Texture* getTexture(std::string name) const; - void store(Texture* texture, std::string name); - - Shader* getShader(std::string name) const; - void store(Shader* shader, std::string name); - - Font* getFont(std::string name) const; - void store(Font* font, std::string name); - - Atlas* getAtlas(std::string name) const; - void store(Atlas* atlas, std::string name); - - audio::Sound* getSound(std::string name) const; - void store(audio::Sound* sound, std::string name); - - const std::vector& getAnimations(); - void store(const TextureAnimation& animation); - - UiDocument* getLayout(std::string name) const; - void store(UiDocument* layout, std::string name); - - void extend(const Assets& assets); -}; - -#endif /* ASSETS_ASSETS_H_ */ diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp new file mode 100644 index 00000000..a9d4b927 --- /dev/null +++ b/src/assets/Assets.hpp @@ -0,0 +1,63 @@ +#ifndef ASSETS_ASSETS_HPP_ +#define ASSETS_ASSETS_HPP_ + +#include "../graphics/core/TextureAnimation.hpp" + +#include +#include +#include +#include +#include + +class Texture; +class Shader; +class Font; +class Atlas; +class Assets; +class UiDocument; + +namespace audio { + class Sound; +} + +namespace assetload { + /// @brief final work to do in the main thread + using postfunc = std::function; +} + +class Assets { + std::unordered_map> textures; + std::unordered_map> shaders; + std::unordered_map> fonts; + std::unordered_map> atlases; + std::unordered_map> layouts; + std::unordered_map> sounds; + std::vector animations; +public: + Assets() {} + Assets(const Assets&) = delete; + ~Assets(); + + Texture* getTexture(std::string name) const; + void store(Texture* texture, std::string name); + + Shader* getShader(std::string name) const; + void store(Shader* shader, std::string name); + + Font* getFont(std::string name) const; + void store(Font* font, std::string name); + + Atlas* getAtlas(std::string name) const; + void store(Atlas* atlas, std::string name); + + audio::Sound* getSound(std::string name) const; + void store(audio::Sound* sound, std::string name); + + const std::vector& getAnimations(); + void store(const TextureAnimation& animation); + + UiDocument* getLayout(std::string name) const; + void store(UiDocument* layout, std::string name); +}; + +#endif // ASSETS_ASSETS_HPP_ diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 1d27e06e..4fb972c6 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -1,58 +1,74 @@ -#include "AssetsLoader.h" -#include "Assets.h" +#include "AssetsLoader.hpp" -#include "assetload_funcs.h" +#include "Assets.hpp" +#include "assetload_funcs.hpp" +#include "../util/ThreadPool.hpp" +#include "../constants.hpp" +#include "../data/dynamic.hpp" +#include "../debug/Logger.hpp" +#include "../coders/imageio.hpp" +#include "../files/files.hpp" +#include "../files/engine_paths.hpp" +#include "../content/Content.hpp" +#include "../content/ContentPack.hpp" +#include "../graphics/core/Texture.hpp" +#include "../logic/scripting/scripting.hpp" #include #include -#include "../constants.h" -#include "../data/dynamic.h" -#include "../files/files.h" -#include "../files/engine_paths.h" -#include "../content/Content.h" -#include "../content/ContentPack.h" -#include "../logic/scripting/scripting.h" +static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) : assets(assets), paths(paths) { - addLoader(AssetType::shader, assetload::shader); - addLoader(AssetType::texture, assetload::texture); - addLoader(AssetType::font, assetload::font); - addLoader(AssetType::atlas, assetload::atlas); + addLoader(AssetType::shader, assetload::shader); + addLoader(AssetType::texture, assetload::texture); + addLoader(AssetType::font, assetload::font); + addLoader(AssetType::atlas, assetload::atlas); addLoader(AssetType::layout, assetload::layout); addLoader(AssetType::sound, assetload::sound); } void AssetsLoader::addLoader(AssetType tag, aloader_func func) { - loaders[tag] = func; + loaders[tag] = func; } void AssetsLoader::add(AssetType tag, const std::string filename, const std::string alias, std::shared_ptr settings) { - entries.push(aloader_entry{tag, filename, alias, settings}); + entries.push(aloader_entry{tag, filename, alias, settings}); } bool AssetsLoader::hasNext() const { - return !entries.empty(); + return !entries.empty(); +} + +aloader_func AssetsLoader::getLoader(AssetType tag) { + auto found = loaders.find(tag); + if (found == loaders.end()) { + throw std::runtime_error( + "unknown asset tag "+std::to_string(static_cast(tag)) + ); + } + return found->second; } bool AssetsLoader::loadNext() { - const aloader_entry& entry = entries.front(); - std::cout << " loading " << entry.filename << " as " << entry.alias << std::endl; - std::cout.flush(); - auto found = loaders.find(entry.tag); - if (found == loaders.end()) { - std::cerr << "unknown asset tag " << static_cast(entry.tag) << std::endl; - return false; - } - aloader_func loader = found->second; - bool status = loader(*this, assets, paths, entry.filename, entry.alias, entry.config); - entries.pop(); - return status; + const aloader_entry& entry = entries.front(); + logger.info() << "loading " << entry.filename << " as " << entry.alias; + try { + aloader_func loader = getLoader(entry.tag); + auto postfunc = loader(this, paths, entry.filename, entry.alias, entry.config); + postfunc(assets); + entries.pop(); + return true; + } catch (std::runtime_error& err) { + logger.error() << err.what(); + entries.pop(); + return false; + } } -void addLayouts(int env, const std::string& prefix, const fs::path& folder, AssetsLoader& loader) { +void addLayouts(scriptenv env, const std::string& prefix, const fs::path& folder, AssetsLoader& loader) { if (!fs::is_directory(folder)) { return; } @@ -117,11 +133,12 @@ void AssetsLoader::processPreloadList(AssetType tag, dynamic::List* list) { auto value = list->get(i); switch (value->type) { case dynamic::valtype::string: - processPreload(tag, *value->value.str, nullptr); + processPreload(tag, std::get(value->value), nullptr); break; case dynamic::valtype::map: { - auto name = value->value.map->getStr("name"); - processPreload(tag, name, value->value.map); + auto map = std::get(value->value); + auto name = map->getStr("name"); + processPreload(tag, name, map); break; } default: @@ -161,9 +178,11 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/menubg", "gui/menubg"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/delete_icon", "gui/delete_icon"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_icon", "gui/no_icon"); + loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_world_icon", "gui/no_world_icon"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/warning", "gui/warning"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/error", "gui/error"); loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/cross", "gui/cross"); + loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/refresh", "gui/refresh"); if (content) { loader.processPreloadConfigs(content); @@ -179,13 +198,68 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { auto pack = entry.second.get(); auto& info = pack->getInfo(); fs::path folder = info.folder / fs::path("layouts"); - addLayouts(pack->getEnvironment()->getId(), info.id, folder, loader); + addLayouts(pack->getEnvironment(), info.id, folder, loader); } } loader.add(AssetType::atlas, TEXTURES_FOLDER+"/blocks", "blocks"); loader.add(AssetType::atlas, TEXTURES_FOLDER+"/items", "items"); } -const ResPaths* AssetsLoader::getPaths() const { - return paths; +bool AssetsLoader::loadExternalTexture( + Assets* assets, + const std::string& name, + std::vector alternatives +) { + if (assets->getTexture(name) != nullptr) { + return true; + } + for (auto& path : alternatives) { + if (fs::exists(path)) { + try { + auto image = imageio::read(path.string()); + assets->store(Texture::from(image.get()).release(), name); + return true; + } catch (const std::exception& err) { + logger.error() << "error while loading external " + << path.u8string() << ": " << err.what(); + } + } + } + return false; +} + +const ResPaths* AssetsLoader::getPaths() const { + return paths; +} + +class LoaderWorker : public util::Worker { + AssetsLoader* loader; +public: + LoaderWorker(AssetsLoader* loader) : loader(loader) { + } + + assetload::postfunc operator()(const std::shared_ptr& entry) override { + aloader_func loadfunc = loader->getLoader(entry->tag); + return loadfunc(loader, loader->getPaths(), entry->filename, entry->alias, entry->config); + } +}; + +std::shared_ptr AssetsLoader::startTask(runnable onDone) { + auto pool = std::make_shared< + util::ThreadPool + >( + "assets-loader-pool", + [=](){return std::make_shared(this);}, + [=](assetload::postfunc& func) { + func(assets); + } + ); + pool->setOnComplete(onDone); + while (!entries.empty()) { + const aloader_entry& entry = entries.front(); + auto ptr = std::make_shared(entry); + pool->enqueueJob(ptr); + entries.pop(); + } + return pool; } diff --git a/src/assets/AssetsLoader.h b/src/assets/AssetsLoader.h deleted file mode 100644 index 5de43150..00000000 --- a/src/assets/AssetsLoader.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef ASSETS_ASSETS_LOADER_H -#define ASSETS_ASSETS_LOADER_H - -#include -#include -#include -#include -#include -#include - -namespace dynamic { - class Map; - class List; -} - -enum class AssetType { - texture, - shader, - font, - atlas, - layout, - sound -}; - -class ResPaths; -class Assets; -class AssetsLoader; -class Content; - -struct AssetCfg { - virtual ~AssetCfg() {} -}; - -struct LayoutCfg : AssetCfg { - int env; - - LayoutCfg(int env) : env(env) {} -}; - -struct SoundCfg : AssetCfg { - bool keepPCM; - - SoundCfg(bool keepPCM) : keepPCM(keepPCM) {} -}; - -using aloader_func = std::function)>; - -struct aloader_entry { - AssetType tag; - const std::string filename; - const std::string alias; - std::shared_ptr config; -}; - -class AssetsLoader { - Assets* assets; - std::map loaders; - std::queue entries; - const ResPaths* paths; - - void tryAddSound(std::string name); - - void processPreload(AssetType tag, const std::string& name, dynamic::Map* map); - void processPreloadList(AssetType tag, dynamic::List* list); - void processPreloadConfig(std::filesystem::path file); - void processPreloadConfigs(const Content* content); -public: - AssetsLoader(Assets* assets, const ResPaths* paths); - void addLoader(AssetType tag, aloader_func func); - - /// @brief Enqueue asset load - /// @param tag asset type - /// @param filename asset file path - /// @param alias internal asset name - /// @param settings asset loading settings (based on asset type) - void add( - AssetType tag, - const std::string filename, - const std::string alias, - std::shared_ptr settings=nullptr - ); - - bool hasNext() const; - bool loadNext(); - - /// @brief Enqueue core and content assets - /// @param loader target loader - /// @param content engine content - static void addDefaults(AssetsLoader& loader, const Content* content); - - const ResPaths* getPaths() const; -}; - -#endif // ASSETS_ASSETS_LOADER_H diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp new file mode 100644 index 00000000..ffb03c03 --- /dev/null +++ b/src/assets/AssetsLoader.hpp @@ -0,0 +1,113 @@ +#ifndef ASSETS_ASSETS_LOADER_HPP_ +#define ASSETS_ASSETS_LOADER_HPP_ + +#include "Assets.hpp" +#include "../interfaces/Task.hpp" +#include "../typedefs.hpp" +#include "../delegates.hpp" + +#include +#include +#include +#include +#include +#include + +namespace dynamic { + class Map; + class List; +} + +enum class AssetType { + texture, + shader, + font, + atlas, + layout, + sound +}; + +class ResPaths; +class AssetsLoader; +class Content; + +struct AssetCfg { + virtual ~AssetCfg() {} +}; + +struct LayoutCfg : AssetCfg { + scriptenv env; + + LayoutCfg(scriptenv env) : env(env) {} +}; + +struct SoundCfg : AssetCfg { + bool keepPCM; + + SoundCfg(bool keepPCM) : keepPCM(keepPCM) {} +}; + +using aloader_func = std::function) +>; + +struct aloader_entry { + AssetType tag; + const std::string filename; + const std::string alias; + std::shared_ptr config; +}; + +class AssetsLoader { + Assets* assets; + std::map loaders; + std::queue entries; + const ResPaths* paths; + + void tryAddSound(std::string name); + + void processPreload(AssetType tag, const std::string& name, dynamic::Map* map); + void processPreloadList(AssetType tag, dynamic::List* list); + void processPreloadConfig(std::filesystem::path file); + void processPreloadConfigs(const Content* content); +public: + AssetsLoader(Assets* assets, const ResPaths* paths); + void addLoader(AssetType tag, aloader_func func); + + /// @brief Enqueue asset load + /// @param tag asset type + /// @param filename asset file path + /// @param alias internal asset name + /// @param settings asset loading settings (based on asset type) + void add( + AssetType tag, + const std::string filename, + const std::string alias, + std::shared_ptr settings=nullptr + ); + + bool hasNext() const; + bool loadNext(); + + std::shared_ptr startTask(runnable onDone); + + const ResPaths* getPaths() const; + aloader_func getLoader(AssetType tag); + + /// @brief Enqueue core and content assets + /// @param loader target loader + /// @param content engine content + static void addDefaults(AssetsLoader& loader, const Content* content); + + static bool loadExternalTexture( + Assets* assets, + const std::string& name, + std::vector alternatives + ); +}; + +#endif // ASSETS_ASSETS_LOADER_HPP_ diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 833b8850..336f2067 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -1,55 +1,54 @@ -#include "assetload_funcs.h" +#include "assetload_funcs.hpp" + +#include "Assets.hpp" +#include "AssetsLoader.hpp" +#include "../data/dynamic.hpp" +#include "../audio/audio.hpp" +#include "../files/files.hpp" +#include "../files/engine_paths.hpp" +#include "../coders/imageio.hpp" +#include "../coders/json.hpp" +#include "../coders/GLSLExtension.hpp" +#include "../graphics/core/Shader.hpp" +#include "../graphics/core/Texture.hpp" +#include "../graphics/core/ImageData.hpp" +#include "../graphics/core/Atlas.hpp" +#include "../graphics/core/Font.hpp" +#include "../graphics/core/TextureAnimation.hpp" +#include "../frontend/UiDocument.hpp" #include +#include #include -#include "Assets.h" -#include "AssetsLoader.h" -#include "../audio/audio.h" -#include "../files/files.h" -#include "../files/engine_paths.h" -#include "../coders/png.h" -#include "../coders/json.h" -#include "../graphics/Shader.h" -#include "../graphics/Texture.h" -#include "../graphics/ImageData.h" -#include "../graphics/Atlas.h" -#include "../graphics/Font.h" -#include "../graphics/TextureAnimation.h" -#include "../frontend/UiDocument.h" -#include "../logic/scripting/scripting.h" namespace fs = std::filesystem; static bool animation( - Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, + Assets* assets, + const ResPaths* paths, + const std::string& atlasName, + const std::string& directory, + const std::string& name, Atlas* dstAtlas ); -bool assetload::texture( - AssetsLoader&, - Assets* assets, +assetload::postfunc assetload::texture( + AssetsLoader*, const ResPaths* paths, - const std::string filename, + const std::string filename, const std::string name, std::shared_ptr ) { - std::unique_ptr texture( - png::load_texture(paths->find(filename+".png").u8string()) + std::shared_ptr image ( + imageio::read(paths->find(filename+".png").u8string()).release() ); - if (texture == nullptr) { - std::cerr << "failed to load texture '" << name << "'" << std::endl; - return false; - } - assets->store(texture.release(), name); - return true; + return [name, image](auto assets) { + assets->store(Texture::from(image.get()).release(), name); + }; } -bool assetload::shader( - AssetsLoader&, - Assets* assets, +assetload::postfunc assetload::shader( + AssetsLoader*, const ResPaths* paths, const std::string filename, const std::string name, @@ -61,43 +60,32 @@ bool assetload::shader( std::string vertexSource = files::read_string(vertexFile); std::string fragmentSource = files::read_string(fragmentFile); - Shader* shader = Shader::create( - vertexFile.string(), - fragmentFile.string(), - vertexSource, fragmentSource - ); + vertexSource = Shader::preprocessor->process(vertexFile, vertexSource); + fragmentSource = Shader::preprocessor->process(fragmentFile, fragmentSource); - if (shader == nullptr) { - std::cerr << "failed to load shader '" << name << "'" << std::endl; - return false; - } - assets->store(shader, name); - return true; + return [=](auto assets) { + assets->store(Shader::create( + vertexFile.u8string(), + fragmentFile.u8string(), + vertexSource, fragmentSource + ), name); + }; } -static bool appendAtlas(AtlasBuilder& atlas, const fs::path& file) { - // png is only supported format - if (file.extension() != ".png") - return false; +static bool append_atlas(AtlasBuilder& atlas, const fs::path& file) { std::string name = file.stem().string(); // skip duplicates if (atlas.has(name)) { return false; } - std::unique_ptr image(png::load_image(file.string())); - if (image == nullptr) { - std::cerr << "could not to load " << file.string() << std::endl; - return false; - } + auto image = imageio::read(file.string()); image->fixAlphaColor(); - atlas.add(name, image.release()); - + atlas.add(name, std::move(image)); return true; } -bool assetload::atlas( - AssetsLoader&, - Assets* assets, +assetload::postfunc assetload::atlas( + AssetsLoader*, const ResPaths* paths, const std::string directory, const std::string name, @@ -105,201 +93,219 @@ bool assetload::atlas( ) { AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { - if (!appendAtlas(builder, file)) continue; + if (!imageio::is_read_supported(file.extension().u8string())) + continue; + if (!append_atlas(builder, file)) + continue; } - Atlas* atlas = builder.build(2); - assets->store(atlas, name); - for (const auto& file : builder.getNames()) { - animation(assets, paths, "textures", file, atlas); - } - return true; + std::set names = builder.getNames(); + Atlas* atlas = builder.build(2, false).release(); + return [=](auto assets) { + atlas->prepare(); + assets->store(atlas, name); + for (const auto& file : names) { + animation(assets, paths, name, directory, file, atlas); + } + }; } -bool assetload::font( - AssetsLoader&, - Assets* assets, +assetload::postfunc assetload::font( + AssetsLoader*, const ResPaths* paths, const std::string filename, const std::string name, std::shared_ptr ) { - std::vector> pages; + auto pages = std::make_shared>>(); for (size_t i = 0; i <= 4; i++) { std::string name = filename + "_" + std::to_string(i) + ".png"; name = paths->find(name).string(); - std::unique_ptr texture (png::load_texture(name)); - if (texture == nullptr) { - std::cerr << "failed to load bitmap font '" << name; - std::cerr << "' (missing page " << std::to_string(i) << ")"; - std::cerr << std::endl; - return false; - } - pages.push_back(std::move(texture)); + pages->push_back(imageio::read(name)); } - int res = pages[0]->getHeight() / 16; - assets->store(new Font(std::move(pages), res, 4), name); - return true; + return [=](auto assets) { + int res = pages->at(0)->getHeight() / 16; + std::vector> textures; + for (auto& page : *pages) { + textures.emplace_back(Texture::from(page.get())); + } + assets->store(new Font(std::move(textures), res, 4), name); + }; } -bool assetload::layout( - AssetsLoader& loader, - Assets* assets, +assetload::postfunc assetload::layout( + AssetsLoader*, const ResPaths* paths, const std::string file, const std::string name, std::shared_ptr config ) { - try { - auto cfg = dynamic_cast(config.get()); - auto document = UiDocument::read(loader, cfg->env, name, file); - assets->store(document.release(), name); - return true; - } catch (const parsing_error& err) { - std::cerr << "failed to parse layout XML '" << file << "'" << std::endl; - std::cerr << err.errorLog() << std::endl; - return false; - } + return [=](auto assets) { + try { + auto cfg = std::dynamic_pointer_cast(config); + auto document = UiDocument::read(cfg->env, name, file); + assets->store(document.release(), name); + } catch (const parsing_error& err) { + throw std::runtime_error( + "failed to parse layout XML '"+file+"':\n"+err.errorLog() + ); + } + }; } -bool assetload::sound( - AssetsLoader& loader, - Assets* assets, +assetload::postfunc assetload::sound( + AssetsLoader*, const ResPaths* paths, const std::string file, const std::string name, std::shared_ptr config ) { - auto cfg = dynamic_cast(config.get()); + auto cfg = std::dynamic_pointer_cast(config); bool keepPCM = cfg ? cfg->keepPCM : false; std::string extension = ".ogg"; - try { - std::unique_ptr baseSound = nullptr; + std::unique_ptr baseSound = nullptr; - // looking for 'sound_name' as base sound - auto soundFile = paths->find(file+extension); - if (fs::exists(soundFile)) { - baseSound.reset(audio::load_sound(soundFile, keepPCM)); - } - // looking for 'sound_name_0' as base sound - auto variantFile = paths->find(file+"_0"+extension); - if (fs::exists(variantFile)) { - baseSound.reset(audio::load_sound(variantFile, keepPCM)); - } - - // loading sound variants - for (uint i = 1; ; i++) { - auto variantFile = paths->find(file+"_"+std::to_string(i)+extension); - if (!fs::exists(variantFile)) { - break; - } - baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); - } - assets->store(baseSound.release(), name); - } - catch (std::runtime_error& err) { - std::cerr << err.what() << std::endl; - return false; + // looking for 'sound_name' as base sound + auto soundFile = paths->find(file+extension); + if (fs::exists(soundFile)) { + baseSound.reset(audio::load_sound(soundFile, keepPCM)); } - return true; + // looking for 'sound_name_0' as base sound + auto variantFile = paths->find(file+"_0"+extension); + if (fs::exists(variantFile)) { + baseSound.reset(audio::load_sound(variantFile, keepPCM)); + } + + // loading sound variants + for (uint i = 1; ; i++) { + auto variantFile = paths->find(file+"_"+std::to_string(i)+extension); + if (!fs::exists(variantFile)) { + break; + } + baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); + } + + if (baseSound == nullptr) { + throw std::runtime_error("could not to find sound: " + file); + } + auto sound = baseSound.release(); + return [=](auto assets) { + assets->store(sound, name); + }; +} + +static void read_anim_file( + const std::string& animFile, + std::vector>& frameList +) { + auto root = files::read_json(animFile); + auto frameArr = root->list("frames"); + float frameDuration = DEFAULT_FRAME_DURATION; + std::string frameName; + + if (frameArr) { + for (size_t i = 0; i < frameArr->size(); i++) { + auto currentFrame = frameArr->list(i); + + frameName = currentFrame->str(0); + if (currentFrame->size() > 1) { + frameDuration = currentFrame->integer(1); + } + frameList.emplace_back(frameName, frameDuration); + } + } +} + +static TextureAnimation create_animation( + Atlas* srcAtlas, + Atlas* dstAtlas, + const std::string& name, + const std::set& frameNames, + const std::vector>& frameList +) { + Texture* srcTex = srcAtlas->getTexture(); + Texture* dstTex = dstAtlas->getTexture(); + UVRegion region = dstAtlas->get(name); + + TextureAnimation animation(srcTex, dstTex); + Frame frame; + + uint dstWidth = dstTex->getWidth(); + uint dstHeight = dstTex->getHeight(); + + uint srcWidth = srcTex->getWidth(); + uint srcHeight = srcTex->getHeight(); + + frame.dstPos = glm::ivec2(region.u1 * dstWidth, region.v1 * dstHeight); + frame.size = glm::ivec2(region.u2 * dstWidth, region.v2 * dstHeight) - frame.dstPos; + + for (const auto& elem : frameList) { + if (!srcAtlas->has(elem.first)) { + std::cerr << "Unknown frame name: " << elem.first << std::endl; + continue; + } + region = srcAtlas->get(elem.first); + if (elem.second > 0) { + frame.duration = static_cast(elem.second) / 1000.0f; + } + frame.srcPos = glm::ivec2(region.u1 * srcWidth, srcHeight - region.v2 * srcHeight); + animation.addFrame(frame); + } + return animation; +} + +inline bool contains( + const std::vector>& frameList, + const std::string& frameName +) { + for (const auto& elem : frameList) { + if (frameName == elem.first) { + return true; + } + } + return false; } static bool animation( Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, + const ResPaths* paths, + const std::string& atlasName, + const std::string& directory, + const std::string& name, Atlas* dstAtlas ) { - std::string animsDir = directory + "/animations"; - std::string blocksDir = directory + "/blocks"; + std::string animsDir = directory + "/animation"; for (const auto& folder : paths->listdir(animsDir)) { if (!fs::is_directory(folder)) continue; - if (folder.filename().string() != name) continue; + if (folder.filename().u8string() != name) continue; if (fs::is_empty(folder)) continue; AtlasBuilder builder; - appendAtlas(builder, paths->find(blocksDir + "/" + name + ".png")); - - std::string animFile = folder.string() + "/animation.json"; - - std::vector> frameList; + append_atlas(builder, paths->find(directory + "/" + name + ".png")); + std::vector> frameList; + std::string animFile = folder.u8string() + "/animation.json"; if (fs::exists(animFile)) { - auto root = files::read_json(animFile); - - auto frameArr = root->list("frames"); - - float frameDuration = DEFAULT_FRAME_DURATION; - std::string frameName; - - if (frameArr) { - for (size_t i = 0; i < frameArr->size(); i++) { - auto currentFrame = frameArr->list(i); - - frameName = currentFrame->str(0); - if (currentFrame->size() > 1) - frameDuration = static_cast(currentFrame->integer(1)) / 1000; - - frameList.emplace_back(frameName, frameDuration); - } - } + read_anim_file(animFile, frameList); } for (const auto& file : paths->listdir(animsDir + "/" + name)) { - if (!frameList.empty()) { - bool contains = false; - for (const auto& elem : frameList) { - if (file.stem() == elem.first) { - contains = true; - break; - } - } - if (!contains) continue; + if (!frameList.empty() && !contains(frameList, file.stem().u8string())) { + continue; } - if (!appendAtlas(builder, file)) continue; + if (!append_atlas(builder, file)) + continue; } - - std::unique_ptr srcAtlas (builder.build(2)); - - Texture* srcTex = srcAtlas->getTexture(); - Texture* dstTex = dstAtlas->getTexture(); - - TextureAnimation animation(srcTex, dstTex); - Frame frame; - UVRegion region = dstAtlas->get(name); - - uint dstWidth = dstTex->getWidth(); - uint dstHeight = dstTex->getHeight(); - - uint srcWidth = srcTex->getWidth(); - uint srcHeight = srcTex->getHeight(); - - frame.dstPos = glm::ivec2(region.u1 * dstWidth, region.v1 * dstHeight); - frame.size = glm::ivec2(region.u2 * dstWidth, region.v2 * dstHeight) - frame.dstPos; - + auto srcAtlas = builder.build(2, true); if (frameList.empty()) { - for (const auto& elem : builder.getNames()) { - region = srcAtlas->get(elem); - frame.srcPos = glm::ivec2(region.u1 * srcWidth, srcHeight - region.v2 * srcHeight); - animation.addFrame(frame); + for (const auto& frameName : builder.getNames()) { + frameList.emplace_back(frameName, 0); } } - else { - for (const auto& elem : frameList) { - if (!srcAtlas->has(elem.first)) { - std::cerr << "Unknown frame name: " << elem.first << std::endl; - continue; - } - region = srcAtlas->get(elem.first); - frame.duration = elem.second; - frame.srcPos = glm::ivec2(region.u1 * srcWidth, srcHeight - region.v2 * srcHeight); - animation.addFrame(frame); - } - } - - assets->store(srcAtlas.release(), name + "_animation"); + auto animation = create_animation( + srcAtlas.get(), dstAtlas, name, builder.getNames(), frameList + ); + assets->store(srcAtlas.release(), atlasName + "/" + name + "_animation"); assets->store(animation); - return true; } return true; diff --git a/src/assets/assetload_funcs.h b/src/assets/assetload_funcs.hpp similarity index 71% rename from src/assets/assetload_funcs.h rename to src/assets/assetload_funcs.hpp index aebdb0a6..c021e1e8 100644 --- a/src/assets/assetload_funcs.h +++ b/src/assets/assetload_funcs.hpp @@ -1,5 +1,7 @@ -#ifndef ASSETS_ASSET_LOADERS_H_ -#define ASSETS_ASSET_LOADERS_H_ +#ifndef ASSETS_ASSET_LOADERS_HPP_ +#define ASSETS_ASSET_LOADERS_HPP_ + +#include "Assets.hpp" #include #include @@ -12,50 +14,44 @@ struct AssetCfg; /// @brief see AssetsLoader.h: aloader_func namespace assetload { - bool texture( - AssetsLoader&, - Assets*, + postfunc texture( + AssetsLoader*, const ResPaths* paths, const std::string filename, const std::string name, std::shared_ptr settings ); - bool shader( - AssetsLoader&, - Assets*, + postfunc shader( + AssetsLoader*, const ResPaths* paths, const std::string filename, const std::string name, std::shared_ptr settings ); - bool atlas( - AssetsLoader&, - Assets*, + postfunc atlas( + AssetsLoader*, const ResPaths* paths, const std::string directory, const std::string name, std::shared_ptr settings ); - bool font( - AssetsLoader&, - Assets*, + postfunc font( + AssetsLoader*, const ResPaths* paths, const std::string filename, const std::string name, std::shared_ptr settings ); - bool layout( - AssetsLoader&, - Assets*, + postfunc layout( + AssetsLoader*, const ResPaths* paths, const std::string file, const std::string name, std::shared_ptr settings ); - bool sound( - AssetsLoader&, - Assets*, + postfunc sound( + AssetsLoader*, const ResPaths* paths, const std::string file, const std::string name, @@ -63,4 +59,4 @@ namespace assetload { ); } -#endif // ASSETS_ASSET_LOADERS_H_ +#endif // ASSETS_ASSET_LOADERS_HPP_ diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 01cc71f6..d4b2fe25 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -1,8 +1,13 @@ -#include "ALAudio.h" -#include "alutil.h" +#include "ALAudio.hpp" + +#include "alutil.hpp" +#include "../../debug/Logger.hpp" + #include #include +static debug::Logger logger("al-audio"); + using namespace audio; ALSound::ALSound(ALAudio* al, uint buffer, std::shared_ptr pcm, bool keepPCM) @@ -203,10 +208,10 @@ ALSpeaker::~ALSpeaker() { } } -void ALSpeaker::update(const Channel* channel, float masterVolume) { +void ALSpeaker::update(const Channel* channel) { if (source == 0) return; - float gain = this->volume * channel->getVolume()*masterVolume; + float gain = this->volume * channel->getVolume(); AL_CHECK(alSourcef(source, AL_GAIN, gain)); if (!paused) { @@ -341,14 +346,14 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]); for (size_t i = 0; i < attrs.size(); ++i){ if (attrs[i] == ALC_MONO_SOURCES) { - std::cout << "AL: max mono sources: " << attrs[i+1] << std::endl; + logger.info() << "max mono sources: " << attrs[i+1]; maxSources = attrs[i+1]; } } auto devices = getAvailableDevices(); - std::cout << "AL devices:" << std::endl; + logger.info() << "devices:"; for (auto& name : devices) { - std::cout << " " << name << std::endl; + logger.info() << " " << name; } } @@ -368,7 +373,7 @@ ALAudio::~ALAudio() { AL_CHECK(alcMakeContextCurrent(context)); alcDestroyContext(context); if (!alcCloseDevice(device)) { - std::cerr << "AL: device not closed!" << std::endl; + logger.error() << "device not closed!"; } device = nullptr; context = nullptr; @@ -395,7 +400,7 @@ ALAudio* ALAudio::create() { return nullptr; } AL_CHECK(); - std::cout << "AL: initialized" << std::endl; + logger.info() << "initialized"; return new ALAudio(device, context); } @@ -406,7 +411,7 @@ uint ALAudio::getFreeSource(){ return source; } if (allsources.size() == maxSources){ - std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl; + logger.error() << "attempted to create new source, but limit is " << maxSources; return 0; } ALuint id; @@ -467,6 +472,7 @@ void ALAudio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, AL_CHECK(alListener3f(AL_POSITION, position.x, position.y, position.z)); AL_CHECK(alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z)); AL_CHECK(alListenerfv(AL_ORIENTATION, listenerOri)); + AL_CHECK(alListenerf(AL_GAIN, get_channel(0)->getVolume())); } void ALAudio::update(double delta) { diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.hpp similarity index 95% rename from src/audio/AL/ALAudio.h rename to src/audio/AL/ALAudio.hpp index 2250088e..b1080cb1 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.hpp @@ -1,5 +1,8 @@ -#ifndef SRC_AUDIO_AUDIO_H_ -#define SRC_AUDIO_AUDIO_H_ +#ifndef SRC_AUDIO_AUDIO_HPP_ +#define SRC_AUDIO_AUDIO_HPP_ + +#include "../audio.hpp" +#include "../../typedefs.hpp" #include #include @@ -15,9 +18,6 @@ #include #endif -#include "../audio.h" -#include "../../typedefs.h" - namespace audio { struct ALBuffer; class ALAudio; @@ -91,7 +91,7 @@ namespace audio { ALSpeaker(ALAudio* al, uint source, int priority, int channel); ~ALSpeaker(); - void update(const Channel* channel, float masterVolume) override; + void update(const Channel* channel) override; int getChannel() const override; State getState() const override; @@ -168,4 +168,4 @@ namespace audio { }; } -#endif /* SRC_AUDIO_AUDIO_H_ */ +#endif // SRC_AUDIO_AUDIO_HPP_ diff --git a/src/audio/AL/alutil.cpp b/src/audio/AL/alutil.cpp index 0dcfce3d..839a7545 100644 --- a/src/audio/AL/alutil.cpp +++ b/src/audio/AL/alutil.cpp @@ -1,4 +1,4 @@ -#include "alutil.h" +#include "alutil.hpp" #include #include diff --git a/src/audio/AL/alutil.h b/src/audio/AL/alutil.hpp similarity index 94% rename from src/audio/AL/alutil.h rename to src/audio/AL/alutil.hpp index 193fa0d3..6ceccda4 100644 --- a/src/audio/AL/alutil.h +++ b/src/audio/AL/alutil.hpp @@ -1,5 +1,7 @@ -#ifndef SRC_AUDIO_AUDIOUTIL_H_ -#define SRC_AUDIO_AUDIOUTIL_H_ +#ifndef AUDIO_AUDIOUTIL_HPP_ +#define AUDIO_AUDIOUTIL_HPP_ + +#include "../../typedefs.hpp" #include #include @@ -12,7 +14,6 @@ #endif #include -#include "../../typedefs.h" #define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__) #define AL_GET_ERROR() AL::check_errors(__FILE__, __LINE__) @@ -80,4 +81,4 @@ namespace AL { } } -#endif /* SRC_AUDIO_AUDIOUTIL_H_ */ +#endif // AUDIO_AUDIOUTIL_HPP_ diff --git a/src/audio/NoAudio.cpp b/src/audio/NoAudio.cpp index b6193fd0..3c40277d 100644 --- a/src/audio/NoAudio.cpp +++ b/src/audio/NoAudio.cpp @@ -1,4 +1,4 @@ -#include "NoAudio.h" +#include "NoAudio.hpp" using namespace audio; diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.hpp similarity index 95% rename from src/audio/NoAudio.h rename to src/audio/NoAudio.hpp index 3029adea..f14ee1c2 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.hpp @@ -1,7 +1,7 @@ -#ifndef AUDIO_NOAUDIO_H_ -#define AUDIO_NOAUDIO_H_ +#ifndef AUDIO_NOAUDIO_HPP_ +#define AUDIO_NOAUDIO_HPP_ -#include "audio.h" +#include "audio.hpp" namespace audio { class NoSound : public Sound { @@ -85,4 +85,4 @@ namespace audio { }; } -#endif // AUDIO_NOAUDIO_H_ +#endif // AUDIO_NOAUDIO_HPP_ diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index b85e4eb1..27ac9de1 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -1,14 +1,14 @@ -#include "audio.h" +#include "audio.hpp" + +#include "NoAudio.hpp" +#include "AL/ALAudio.hpp" + +#include "../coders/wav.hpp" +#include "../coders/ogg.hpp" #include #include -#include "NoAudio.h" -#include "AL/ALAudio.h" - -#include "../coders/wav.h" -#include "../coders/ogg.h" - namespace audio { static speakerid_t nextId = 1; static Backend* backend; @@ -383,13 +383,12 @@ void audio::update(double delta) { entry.second->update(delta); } - float masterVolume = channels.at(0)->getVolume(); for (auto it = speakers.begin(); it != speakers.end();) { auto speaker = it->second.get(); int speakerChannel = speaker->getChannel(); auto channel = get_channel(speakerChannel); if (channel != nullptr) { - speaker->update(channel, speakerChannel == 0 ? 1.0f : masterVolume); + speaker->update(channel); } if (speaker->isStopped()) { streams.erase(it->first); diff --git a/src/audio/audio.h b/src/audio/audio.hpp similarity index 92% rename from src/audio/audio.h rename to src/audio/audio.hpp index 12abf9d7..4a5ca9e0 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.hpp @@ -1,11 +1,12 @@ -#ifndef AUDIO_AUDIO_H_ -#define AUDIO_AUDIO_H_ +#ifndef AUDIO_AUDIO_HPP_ +#define AUDIO_AUDIO_HPP_ + +#include "../typedefs.hpp" #include #include #include #include -#include "../typedefs.h" namespace fs = std::filesystem; @@ -235,8 +236,7 @@ namespace audio { /// @brief Synchronize the speaker with channel settings /// @param channel speaker channel - /// @param masterVolume volume of the master channel - virtual void update(const Channel* channel, float masterVolume) = 0; + virtual void update(const Channel* channel) = 0; /// @brief Check speaker channel index virtual int getChannel() const = 0; @@ -353,52 +353,52 @@ namespace audio { /// @brief Initialize audio system or use no audio mode /// @param enabled try to initialize actual audio - extern void initialize(bool enabled); + void initialize(bool enabled); /// @brief Load audio file info and PCM data /// @param file audio file /// @param headerOnly read header only /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return PCM audio data - extern PCM* load_PCM(const fs::path& file, bool headerOnly); + PCM* load_PCM(const fs::path& file, bool headerOnly); /// @brief Load sound from file /// @param file audio file path /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new Sound instance - extern Sound* load_sound(const fs::path& file, bool keepPCM); + Sound* load_sound(const fs::path& file, bool keepPCM); /// @brief Create new sound from PCM data /// @param pcm PCM data /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM /// @return new Sound instance - extern Sound* create_sound(std::shared_ptr pcm, bool keepPCM); + Sound* create_sound(std::shared_ptr pcm, bool keepPCM); /// @brief Open new PCM stream from file /// @param file audio file path /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new PCMStream instance - extern PCMStream* open_PCM_stream(const fs::path& file); + PCMStream* open_PCM_stream(const fs::path& file); /// @brief Open new audio stream from file /// @param file audio file path /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource /// @return new Stream instance - extern Stream* open_stream(const fs::path& file, bool keepSource); + Stream* open_stream(const fs::path& file, bool keepSource); /// @brief Open new audio stream from source /// @param stream PCM data source /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource /// @return new Stream instance - extern Stream* open_stream(std::shared_ptr stream, bool keepSource); + Stream* open_stream(std::shared_ptr stream, bool keepSource); /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) /// @param lookAt point the listener look at /// @param up camera up vector - extern void set_listener( + void set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, @@ -416,7 +416,7 @@ namespace audio { /// (PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH) /// @param channel channel index /// @return speaker id or 0 - extern speakerid_t play( + speakerid_t play( Sound* sound, glm::vec3 position, bool relative, @@ -436,7 +436,7 @@ namespace audio { /// @param loop loop stream /// @param channel channel index /// @return speaker id or 0 - extern speakerid_t play( + speakerid_t play( std::shared_ptr stream, glm::vec3 position, bool relative, @@ -455,7 +455,7 @@ namespace audio { /// @param loop loop stream /// @param channel channel index /// @return speaker id or 0 - extern speakerid_t play_stream( + speakerid_t play_stream( const fs::path& file, glm::vec3 position, bool relative, @@ -468,49 +468,49 @@ namespace audio { /// @brief Get speaker by id /// @param id speaker id /// @return speaker or nullptr - extern Speaker* get_speaker(speakerid_t id); + Speaker* get_speaker(speakerid_t id); /// @brief Create new channel. /// All non-builtin channels will be destroyed on audio::reset() call /// @param name channel name /// @return new channel index - extern int create_channel(const std::string& name); + int create_channel(const std::string& name); /// @brief Get channel index by name /// @param name channel name /// @return channel index or -1 - extern int get_channel_index(const std::string& name); + int get_channel_index(const std::string& name); /// @brief Get channel by index. 0 - is master channel /// @param index channel index /// @return channel or nullptr - extern Channel* get_channel(int index); + Channel* get_channel(int index); /// @brief Get channel by name. /// @param name channel name /// @return channel or nullptr - extern Channel* get_channel(const std::string& name); + Channel* get_channel(const std::string& name); /// @brief Get stream associated with speaker /// @param id speaker id /// @return stream or nullptr - extern std::shared_ptr get_associated_stream(speakerid_t id); + std::shared_ptr get_associated_stream(speakerid_t id); /// @brief Get alive speakers number (including paused) - extern size_t count_speakers(); + size_t count_speakers(); /// @brief Get playing streams number (including paused) - extern size_t count_streams(); + size_t count_streams(); /// @brief Update audio streams and sound instanced /// @param delta time elapsed since the last update (seconds) - extern void update(double delta); + void update(double delta); /// @brief Stop all playing audio in channel, reset channel state - extern void reset_channel(int channel); + void reset_channel(int channel); /// @brief Finalize audio system - extern void close(); + void close(); }; -#endif // AUDIO_AUDIO_H_ +#endif // AUDIO_AUDIO_HPP_ diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index a297fd98..84aece78 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -1,11 +1,13 @@ -#include "GLSLExtension.h" +#include "GLSLExtension.hpp" + +#include "../util/stringutil.hpp" +#include "../typedefs.hpp" +#include "../files/files.hpp" +#include "../files/engine_paths.hpp" + #include #include #include -#include "../util/stringutil.h" -#include "../typedefs.h" -#include "../files/files.h" -#include "../files/engine_paths.h" namespace fs = std::filesystem; @@ -81,7 +83,7 @@ inline void source_line(std::stringstream& ss, uint linenum) { ss << "#line " << linenum << "\n"; } -const std::string GLSLExtension::process(const fs::path file, const std::string& source) { +const std::string GLSLExtension::process(const fs::path& file, const std::string& source) { std::stringstream ss; size_t pos = 0; uint linenum = 1; diff --git a/src/coders/GLSLExtension.h b/src/coders/GLSLExtension.hpp similarity index 79% rename from src/coders/GLSLExtension.h rename to src/coders/GLSLExtension.hpp index 3b4c494f..2a106d38 100644 --- a/src/coders/GLSLExtension.h +++ b/src/coders/GLSLExtension.hpp @@ -1,5 +1,5 @@ -#ifndef CODERS_GLSL_EXTESION_H_ -#define CODERS_GLSL_EXTESION_H_ +#ifndef CODERS_GLSL_EXTESION_HPP_ +#define CODERS_GLSL_EXTESION_HPP_ #include #include @@ -29,7 +29,10 @@ public: bool hasHeader(const std::string& name) const; bool hasDefine(const std::string& name) const; - const std::string process(const std::filesystem::path file, const std::string& source); + const std::string process( + const std::filesystem::path& file, + const std::string& source + ); }; -#endif // CODERS_GLSL_EXTESION_H_ +#endif // CODERS_GLSL_EXTESION_HPP_ diff --git a/src/coders/binary_json.cpp b/src/coders/binary_json.cpp index 04d91dfa..c6e7f533 100644 --- a/src/coders/binary_json.cpp +++ b/src/coders/binary_json.cpp @@ -1,29 +1,32 @@ -#include "binary_json.h" +#include "binary_json.hpp" + +#include "gzip.hpp" +#include "byte_utils.hpp" +#include "../data/dynamic.hpp" #include -#include "gzip.h" -#include "byte_utils.h" - using namespace json; using namespace dynamic; static void to_binary(ByteBuilder& builder, const Value* value) { switch (value->type) { + case valtype::none: + throw std::runtime_error("none value is not implemented"); case valtype::map: { - std::vector bytes = to_binary(value->value.map); + std::vector bytes = to_binary(std::get(value->value)); builder.put(bytes.data(), bytes.size()); break; } case valtype::list: builder.put(BJSON_TYPE_LIST); - for (auto& element : value->value.list->values) { + for (auto& element : std::get(value->value)->values) { to_binary(builder, element.get()); } builder.put(BJSON_END); break; case valtype::integer: { - int64_t val = value->value.integer; + auto val = std::get(value->value); if (val >= 0 && val <= 255) { builder.put(BJSON_TYPE_BYTE); builder.put(val); @@ -41,14 +44,14 @@ static void to_binary(ByteBuilder& builder, const Value* value) { } case valtype::number: builder.put(BJSON_TYPE_NUMBER); - builder.putFloat64(value->value.decimal); + builder.putFloat64(std::get(value->value)); break; case valtype::boolean: - builder.put(BJSON_TYPE_FALSE + value->value.boolean); + builder.put(BJSON_TYPE_FALSE + std::get(value->value)); break; case valtype::string: builder.put(BJSON_TYPE_STRING); - builder.put(*value->value.str); + builder.put(std::get(value->value)); break; } } @@ -88,40 +91,40 @@ static Value* value_from_binary(ByteReader& reader) { case BJSON_TYPE_DOCUMENT: type = valtype::map; reader.getInt32(); - val.map = object_from_binary(reader); + val = object_from_binary(reader); break; case BJSON_TYPE_LIST: type = valtype::list; - val.list = array_from_binary(reader); + val = array_from_binary(reader); break; case BJSON_TYPE_BYTE: type = valtype::integer; - val.integer = reader.get(); + val = static_cast(reader.get()); break; case BJSON_TYPE_INT16: type = valtype::integer; - val.integer = reader.getInt16(); + val = static_cast(reader.getInt16()); break; case BJSON_TYPE_INT32: type = valtype::integer; - val.integer = reader.getInt32(); + val = static_cast(reader.getInt32()); break; case BJSON_TYPE_INT64: type = valtype::integer; - val.integer = reader.getInt64(); + val = reader.getInt64(); break; case BJSON_TYPE_NUMBER: type = valtype::number; - val.decimal = reader.getFloat64(); + val = reader.getFloat64(); break; case BJSON_TYPE_FALSE: case BJSON_TYPE_TRUE: type = valtype::boolean; - val.boolean = typecode - BJSON_TYPE_FALSE; + val = (typecode - BJSON_TYPE_FALSE) != 0; break; case BJSON_TYPE_STRING: type = valtype::string; - val.str = new std::string(reader.getString()); + val = reader.getString(); break; default: throw std::runtime_error( @@ -166,8 +169,8 @@ std::unique_ptr json::from_binary(const ubyte* src, size_t size) { if (value->type != valtype::map) { throw std::runtime_error("root value is not an object"); } - std::unique_ptr obj (value->value.map); - value->value.map = nullptr; + std::unique_ptr obj (std::get(value->value)); + value->value = (Map*)nullptr; return obj; } } diff --git a/src/coders/binary_json.h b/src/coders/binary_json.hpp similarity index 82% rename from src/coders/binary_json.h rename to src/coders/binary_json.hpp index 913e71ec..0c3056bb 100644 --- a/src/coders/binary_json.h +++ b/src/coders/binary_json.hpp @@ -1,9 +1,14 @@ -#ifndef CODERS_BINARY_JSON_H_ -#define CODERS_BINARY_JSON_H_ +#ifndef CODERS_BINARY_JSON_HPP_ +#define CODERS_BINARY_JSON_HPP_ + +#include "../typedefs.hpp" #include #include -#include "../data/dynamic.h" + +namespace dynamic { + class Map; +} namespace json { const int BJSON_END = 0x0; @@ -25,4 +30,4 @@ namespace json { extern std::unique_ptr from_binary(const ubyte* src, size_t size); } -#endif // CODERS_BINARY_JSON_H_ +#endif // CODERS_BINARY_JSON_HPP_ diff --git a/src/coders/byte_utils.cpp b/src/coders/byte_utils.cpp index ce52a156..670f5bca 100644 --- a/src/coders/byte_utils.cpp +++ b/src/coders/byte_utils.cpp @@ -1,4 +1,4 @@ -#include "byte_utils.h" +#include "byte_utils.hpp" #include #include diff --git a/src/coders/byte_utils.h b/src/coders/byte_utils.hpp similarity index 72% rename from src/coders/byte_utils.h rename to src/coders/byte_utils.hpp index 4d651892..068170af 100644 --- a/src/coders/byte_utils.h +++ b/src/coders/byte_utils.hpp @@ -1,9 +1,10 @@ -#ifndef CODERS_BYTE_UTILS_H_ -#define CODERS_BYTE_UTILS_H_ +#ifndef CODERS_BYTE_UTILS_HPP_ +#define CODERS_BYTE_UTILS_HPP_ + +#include "../typedefs.hpp" #include #include -#include "../typedefs.h" /* byteorder: little-endian */ class ByteBuilder { @@ -44,7 +45,7 @@ public: std::vector build(); }; -/* byteorder: little-endian */ +/// byteorder: little-endian class ByteReader { const ubyte* data; size_t size; @@ -54,26 +55,29 @@ public: ByteReader(const ubyte* data); void checkMagic(const char* data, size_t size); - /* Read one byte (unsigned 8 bit integer) */ + /// @brief Read one byte (unsigned 8 bit integer) ubyte get(); - /* Read one byte (unsigned 8 bit integer) without pointer move */ + /// @brief Read one byte (unsigned 8 bit integer) without pointer move ubyte peek(); - /* Read signed 16 bit integer */ + /// @brief Read signed 16 bit integer int16_t getInt16(); - /* Read signed 32 bit integer */ + /// @brief Read signed 32 bit integer int32_t getInt32(); - /* Read signed 64 bit integer */ + /// @brief Read signed 64 bit integer int64_t getInt64(); - /* Read 32 bit floating-point number */ + /// @brief Read 32 bit floating-point number float getFloat32(); - /* Read 64 bit floating-point number */ + /// @brief Read 64 bit floating-point number double getFloat64(); + /// @brief Read C-String const char* getCString(); + /// @brief Read string with unsigned 32 bit number before (length) std::string getString(); + /// @return true if there is at least one byte remains bool hasNext() const; const ubyte* pointer() const; void skip(size_t n); }; -#endif // CODERS_BYTE_UTILS_H_ +#endif // CODERS_BYTE_UTILS_HPP_ diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index ad4a8677..2f700b57 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -1,344 +1,331 @@ -#include "commons.h" - -#include -#include -#include - -inline double power(double base, int64_t power) { - double result = 1.0; - for (int64_t i = 0; i < power; i++) { - result *= base; - } - return result; -} - -parsing_error::parsing_error( - std::string message, - std::string filename, - std::string source, - uint pos, - uint line, - uint linestart) - : std::runtime_error(message), filename(filename), source(source), - pos(pos), line(line), linestart(linestart) { -} - -std::string parsing_error::errorLog() const { - std::stringstream ss; - uint linepos = pos - linestart; - ss << "parsing error in file '" << filename; - ss << "' at " << (line+1) << ":" << linepos << ": " << this->what() << "\n"; - size_t end = source.find("\n", linestart); - if (end == std::string::npos) { - end = source.length(); - } - ss << source.substr(linestart, end-linestart) << "\n"; - for (uint i = 0; i < linepos; i++) { - ss << " "; - } - ss << "^"; - return ss.str(); - -} - -std::string escape_string(std::string s) { - std::stringstream ss; - ss << '"'; - for (char c : s) { - switch (c) { - case '\n': ss << "\\n"; break; - case '\r': ss << "\\r"; break; - case '\t': ss << "\\t"; break; - case '\f': ss << "\\f"; break; - case '\b': ss << "\\b"; break; - case '"': ss << "\\\""; break; - case '\\': ss << "\\\\"; break; - default: - if (c < ' ') { - ss << "\\" << std::oct << uint(ubyte(c)); - break; - } - ss << c; - break; - } - } - ss << '"'; - return ss.str(); -} - -BasicParser::BasicParser(std::string file, std::string source) : filename(file), source(source) { -} - -void BasicParser::skipWhitespace() { - while (hasNext()) { - char next = source[pos]; - if (next == '\n') { - line++; - linestart = ++pos; - continue; - } - if (is_whitespace(next)) { - pos++; - } else { - break; - } - } -} - -void BasicParser::skip(size_t n) { - n = std::min(n, source.length()-pos); - - for (size_t i = 0; i < n; i++) { - char next = source[pos++]; - if (next == '\n') { - line++; - linestart = pos; - } - } -} - -void BasicParser::skipLine() { - while (hasNext()) { - if (source[pos] == '\n') { - pos++; - linestart = pos; - line++; - break; - } - pos++; - } -} - -bool BasicParser::skipTo(const std::string& substring) { - size_t idx = source.find(substring, pos); - if (idx == std::string::npos) { - skip(source.length()-pos); - return false; - } else { - skip(idx-pos); - return true; - } -} - -bool BasicParser::hasNext() { - return pos < source.length(); -} - -bool BasicParser::isNext(const std::string& substring) { - if (source.length() - pos < substring.length()) { - return false; - } - return source.substr(pos, substring.length()) == substring; -} - -char BasicParser::nextChar() { - if (!hasNext()) { - throw error("unexpected end"); - } - return source[pos++]; -} - -void BasicParser::expect(char expected) { - char c = peek(); - if (c != expected) { - throw error("'"+std::string({expected})+"' expected"); - } - pos++; -} - -void BasicParser::expect(const std::string& substring) { - if (substring.empty()) - return; - for (uint i = 0; i < substring.length(); i++) { - if (source.length() <= pos + i || source[pos+i] != substring[i]) { - throw error(escape_string(substring)+" expected"); - } - } - pos += substring.length(); -} - -void BasicParser::expectNewLine() { - while (hasNext()) { - char next = source[pos]; - if (next == '\n') { - line++; - linestart = ++pos; - return; - } - if (is_whitespace(next)) { - pos++; - } else { - throw error("line separator expected"); - } - } -} - -void BasicParser::goBack() { - if (pos) pos--; -} - -char BasicParser::peek() { - skipWhitespace(); - if (pos >= source.length()) { - throw error("unexpected end"); - } - return source[pos]; -} - -std::string BasicParser::parseName() { - char c = peek(); - if (!is_identifier_start(c)) { - if (c == '"') { - pos++; - return parseString(c); - } - throw error("identifier expected"); - } - int start = pos; - while (hasNext() && is_identifier_part(source[pos])) { - pos++; - } - return source.substr(start, pos-start); -} - -int64_t BasicParser::parseSimpleInt(int base) { - char c = peek(); - int index = hexchar2int(c); - if (index == -1 || index >= base) { - throw error("invalid number literal"); - } - int64_t value = index; - pos++; - while (hasNext()) { - c = source[pos]; - while (c == '_') { - c = source[++pos]; - } - index = hexchar2int(c); - if (index == -1 || index >= base) { - return value; - } - value *= base; - value += index; - pos++; - } - return value; -} - -bool BasicParser::parseNumber(int sign, number_u& out) { - char c = peek(); - int base = 10; - if (c == '0' && pos + 1 < source.length() && - (base = is_box(source[pos+1])) != 10) { - pos += 2; - out.ival = parseSimpleInt(base); - return true; - } else if (c == 'i' && pos + 2 < source.length() && source[pos+1] == 'n' && source[pos+2] == 'f') { - pos += 3; - out.fval = INFINITY * sign; - return false; - } else if (c == 'n' && pos + 2 < source.length() && source[pos+1] == 'a' && source[pos+2] == 'n') { - pos += 3; - out.fval = NAN * sign; - return false; - } - int64_t value = parseSimpleInt(base); - if (!hasNext()) { - out.ival = value * sign; - return true; - } - c = source[pos]; - if (c == 'e' || c == 'E') { - pos++; - int s = 1; - if (peek() == '-') { - s = -1; - pos++; - } else if (peek() == '+'){ - pos++; - } - out.fval = sign * value * power(10.0, s * parseSimpleInt(10)); - return false; - } - if (c == '.') { - pos++; - int64_t expo = 1; - while (hasNext() && source[pos] == '0') { - expo *= 10; - pos++; - } - int64_t afterdot = 0; - if (hasNext() && is_digit(source[pos])) { - afterdot = parseSimpleInt(10); - } - expo *= power(10, fmax(0, log10(afterdot) + 1)); - c = source[pos]; - - double dvalue = (value + (afterdot / (double)expo)); - if (c == 'e' || c == 'E') { - pos++; - int s = 1; - if (peek() == '-') { - s = -1; - pos++; - } else if (peek() == '+'){ - pos++; - } - out.fval = sign * dvalue * power(10.0, s * parseSimpleInt(10)); - return false; - } - out.fval = sign * dvalue; - return false; - } - out.ival = sign * value; - return true; -} - -std::string BasicParser::parseString(char quote, bool closeRequired) { - std::stringstream ss; - while (hasNext()) { - char c = source[pos]; - if (c == quote) { - pos++; - return ss.str(); - } - if (c == '\\') { - pos++; - c = nextChar(); - if (c >= '0' && c <= '7') { - pos--; - ss << (char)parseSimpleInt(8); - continue; - } - switch (c) { - case 'n': ss << '\n'; break; - case 'r': ss << '\r'; break; - case 'b': ss << '\b'; break; - case 't': ss << '\t'; break; - case 'f': ss << '\f'; break; - case '\'': ss << '\\'; break; - case '"': ss << '"'; break; - case '\\': ss << '\\'; break; - case '/': ss << '/'; break; - case '\n': pos++; continue; - default: - throw error("'\\" + std::string({c}) + - "' is an illegal escape"); - } - continue; - } - if (c == '\n' && closeRequired) { - throw error("non-closed string literal"); - } - ss << c; - pos++; - } - if (closeRequired) { - throw error("unexpected end"); - } - return ss.str(); -} - -parsing_error BasicParser::error(std::string message) { - return parsing_error(message, filename, source, pos, line, linestart); -} +#include "commons.hpp" + +#include "../util/stringutil.hpp" + +#include +#include +#include + +inline double power(double base, int64_t power) { + double result = 1.0; + for (int64_t i = 0; i < power; i++) { + result *= base; + } + return result; +} + +parsing_error::parsing_error( + std::string message, + std::string filename, + std::string source, + uint pos, + uint line, + uint linestart) + : std::runtime_error(message), filename(filename), source(source), + pos(pos), line(line), linestart(linestart) { +} + +std::string parsing_error::errorLog() const { + std::stringstream ss; + uint linepos = pos - linestart; + ss << "parsing error in file '" << filename; + ss << "' at " << (line+1) << ":" << linepos << ": " << this->what() << "\n"; + size_t end = source.find("\n", linestart); + if (end == std::string::npos) { + end = source.length(); + } + ss << source.substr(linestart, end-linestart) << "\n"; + for (uint i = 0; i < linepos; i++) { + ss << " "; + } + ss << "^"; + return ss.str(); +} + +BasicParser::BasicParser( + const std::string& file, + const std::string& source +) : filename(file), source(source) { +} + +void BasicParser::skipWhitespace() { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + line++; + linestart = ++pos; + continue; + } + if (is_whitespace(next)) { + pos++; + } else { + break; + } + } +} + +void BasicParser::skip(size_t n) { + n = std::min(n, source.length()-pos); + + for (size_t i = 0; i < n; i++) { + char next = source[pos++]; + if (next == '\n') { + line++; + linestart = pos; + } + } +} + +void BasicParser::skipLine() { + while (hasNext()) { + if (source[pos] == '\n') { + pos++; + linestart = pos; + line++; + break; + } + pos++; + } +} + +bool BasicParser::skipTo(const std::string& substring) { + size_t idx = source.find(substring, pos); + if (idx == std::string::npos) { + skip(source.length()-pos); + return false; + } else { + skip(idx-pos); + return true; + } +} + +bool BasicParser::hasNext() { + return pos < source.length(); +} + +bool BasicParser::isNext(const std::string& substring) { + if (source.length() - pos < substring.length()) { + return false; + } + return source.substr(pos, substring.length()) == substring; +} + +char BasicParser::nextChar() { + if (!hasNext()) { + throw error("unexpected end"); + } + return source[pos++]; +} + +void BasicParser::expect(char expected) { + char c = peek(); + if (c != expected) { + throw error("'"+std::string({expected})+"' expected"); + } + pos++; +} + +void BasicParser::expect(const std::string& substring) { + if (substring.empty()) + return; + for (uint i = 0; i < substring.length(); i++) { + if (source.length() <= pos + i || source[pos+i] != substring[i]) { + throw error(util::quote(substring)+" expected"); + } + } + pos += substring.length(); +} + +void BasicParser::expectNewLine() { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + line++; + linestart = ++pos; + return; + } + if (is_whitespace(next)) { + pos++; + } else { + throw error("line separator expected"); + } + } +} + +void BasicParser::goBack() { + if (pos) pos--; +} + +char BasicParser::peek() { + skipWhitespace(); + if (pos >= source.length()) { + throw error("unexpected end"); + } + return source[pos]; +} + +std::string BasicParser::readUntil(char c) { + int start = pos; + while (hasNext() && source[pos] != c) { + pos++; + } + return source.substr(start, pos-start); +} + +std::string BasicParser::parseName() { + char c = peek(); + if (!is_identifier_start(c)) { + if (c == '"') { + pos++; + return parseString(c); + } + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_identifier_part(source[pos])) { + pos++; + } + return source.substr(start, pos-start); +} + +int64_t BasicParser::parseSimpleInt(int base) { + char c = peek(); + int index = hexchar2int(c); + if (index == -1 || index >= base) { + throw error("invalid number literal"); + } + int64_t value = index; + pos++; + while (hasNext()) { + c = source[pos]; + while (c == '_') { + c = source[++pos]; + } + index = hexchar2int(c); + if (index == -1 || index >= base) { + return value; + } + value *= base; + value += index; + pos++; + } + return value; +} + +bool BasicParser::parseNumber(int sign, number_u& out) { + char c = peek(); + int base = 10; + if (c == '0' && pos + 1 < source.length() && + (base = is_box(source[pos+1])) != 10) { + pos += 2; + out = parseSimpleInt(base); + return true; + } else if (c == 'i' && pos + 2 < source.length() && source[pos+1] == 'n' && source[pos+2] == 'f') { + pos += 3; + out = INFINITY * sign; + return false; + } else if (c == 'n' && pos + 2 < source.length() && source[pos+1] == 'a' && source[pos+2] == 'n') { + pos += 3; + out = NAN * sign; + return false; + } + int64_t value = parseSimpleInt(base); + if (!hasNext()) { + out = value * sign; + return true; + } + c = source[pos]; + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + out = sign * value * power(10.0, s * parseSimpleInt(10)); + return false; + } + if (c == '.') { + pos++; + int64_t expo = 1; + while (hasNext() && source[pos] == '0') { + expo *= 10; + pos++; + } + int64_t afterdot = 0; + if (hasNext() && is_digit(source[pos])) { + afterdot = parseSimpleInt(10); + } + expo *= power(10, fmax(0, log10(afterdot) + 1)); + c = source[pos]; + + double dvalue = (value + (afterdot / (double)expo)); + if (c == 'e' || c == 'E') { + pos++; + int s = 1; + if (peek() == '-') { + s = -1; + pos++; + } else if (peek() == '+'){ + pos++; + } + out = sign * dvalue * power(10.0, s * parseSimpleInt(10)); + return false; + } + out = sign * dvalue; + return false; + } + out = sign * value; + return true; +} + +std::string BasicParser::parseString(char quote, bool closeRequired) { + std::stringstream ss; + while (hasNext()) { + char c = source[pos]; + if (c == quote) { + pos++; + return ss.str(); + } + if (c == '\\') { + pos++; + c = nextChar(); + if (c >= '0' && c <= '7') { + pos--; + ss << (char)parseSimpleInt(8); + continue; + } + switch (c) { + case 'n': ss << '\n'; break; + case 'r': ss << '\r'; break; + case 'b': ss << '\b'; break; + case 't': ss << '\t'; break; + case 'f': ss << '\f'; break; + case '\'': ss << '\\'; break; + case '"': ss << '"'; break; + case '\\': ss << '\\'; break; + case '/': ss << '/'; break; + case '\n': pos++; continue; + default: + throw error("'\\" + std::string({c}) + + "' is an illegal escape"); + } + continue; + } + if (c == '\n' && closeRequired) { + throw error("non-closed string literal"); + } + ss << c; + pos++; + } + if (closeRequired) { + throw error("unexpected end"); + } + return ss.str(); +} + +parsing_error BasicParser::error(std::string message) { + return parsing_error(message, filename, source, pos, line, linestart); +} diff --git a/src/coders/commons.h b/src/coders/commons.hpp similarity index 73% rename from src/coders/commons.h rename to src/coders/commons.hpp index 509ff0d6..dc1998eb 100644 --- a/src/coders/commons.h +++ b/src/coders/commons.hpp @@ -1,14 +1,10 @@ -#ifndef CODERS_COMMONS_H_ -#define CODERS_COMMONS_H_ +#ifndef CODERS_COMMONS_HPP_ +#define CODERS_COMMONS_HPP_ + +#include "../typedefs.hpp" #include #include -#include "../typedefs.h" - -union number_u { - double fval; - int64_t ival; -}; inline int is_box(int c) { switch (c) { @@ -54,8 +50,6 @@ inline int hexchar2int(int c) { return -1; } -extern std::string escape_string(std::string s); - class parsing_error : public std::runtime_error { public: std::string filename; @@ -64,20 +58,21 @@ public: uint line; uint linestart; - parsing_error(std::string message, - std::string filename, - std::string source, - uint pos, - uint line, - uint linestart); - + parsing_error( + std::string message, + std::string filename, + std::string source, + uint pos, + uint line, + uint linestart + ); std::string errorLog() const; }; class BasicParser { protected: - std::string filename; - std::string source; + const std::string& filename; + const std::string& source; uint pos = 0; uint line = 1; uint linestart = 0; @@ -88,21 +83,24 @@ protected: bool skipTo(const std::string& substring); void expect(char expected); void expect(const std::string& substring); - char peek(); - char nextChar(); - bool hasNext(); bool isNext(const std::string& substring); void expectNewLine(); void goBack(); - std::string parseName(); int64_t parseSimpleInt(int base); bool parseNumber(int sign, number_u& out); std::string parseString(char chr, bool closeRequired=true); parsing_error error(std::string message); - BasicParser(std::string filename, std::string source); +public: + std::string readUntil(char c); + std::string parseName(); + bool hasNext(); + char peek(); + char nextChar(); + + BasicParser(const std::string& file, const std::string& source); }; -#endif // CODERS_COMMONS_H_ \ No newline at end of file +#endif // CODERS_COMMONS_HPP_ diff --git a/src/coders/gzip.cpp b/src/coders/gzip.cpp index ee1afd62..c9608b53 100644 --- a/src/coders/gzip.cpp +++ b/src/coders/gzip.cpp @@ -1,10 +1,11 @@ -#include "gzip.h" +#include "gzip.hpp" + +#include "byte_utils.hpp" #define ZLIB_CONST #include #include #include -#include "byte_utils.h" std::vector gzip::compress(const ubyte* src, size_t size) { size_t buffer_size = 23+size*1.01; diff --git a/src/coders/gzip.h b/src/coders/gzip.hpp similarity index 81% rename from src/coders/gzip.h rename to src/coders/gzip.hpp index 75f3618c..d7b3e944 100644 --- a/src/coders/gzip.h +++ b/src/coders/gzip.hpp @@ -1,8 +1,8 @@ -#ifndef CODERS_GZIP_H_ -#define CODERS_GZIP_H_ +#ifndef CODERS_GZIP_HPP_ +#define CODERS_GZIP_HPP_ +#include "../typedefs.hpp" #include -#include "../typedefs.h" namespace gzip { const unsigned char MAGIC[] = "\x1F\x8B"; @@ -18,4 +18,4 @@ namespace gzip { std::vector decompress(const ubyte* src, size_t size); } -#endif // CODERS_GZIP_H_ +#endif // CODERS_GZIP_HPP_ diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp new file mode 100644 index 00000000..cfddb3b2 --- /dev/null +++ b/src/coders/imageio.cpp @@ -0,0 +1,49 @@ +#include "imageio.hpp" + +#include "png.hpp" +#include "../graphics/core/ImageData.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +using image_reader = std::function(const std::string&)>; +using image_writer = std::function; + +static std::unordered_map readers { + {".png", png::load_image}, +}; + +static std::unordered_map writers { + {".png", png::write_image}, +}; + +bool imageio::is_read_supported(const std::string& extension) { + return readers.find(extension) != readers.end(); +} + +bool imageio::is_write_supported(const std::string& extension) { + return writers.find(extension) != writers.end(); +} + +inline std::string extensionOf(const std::string& filename) { + return fs::u8path(filename).extension().u8string(); +} + +std::unique_ptr imageio::read(const std::string& filename) { + auto found = readers.find(extensionOf(filename)); + if (found == readers.end()) { + throw std::runtime_error("file format is not supported (read): "+filename); + } + return std::unique_ptr(found->second(filename)); +} + +void imageio::write(const std::string& filename, const ImageData* image) { + auto found = writers.find(extensionOf(filename)); + if (found == writers.end()) { + throw std::runtime_error("file format is not supported (write): "+filename); + } + return found->second(filename, image); +} diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp new file mode 100644 index 00000000..a05cfadc --- /dev/null +++ b/src/coders/imageio.hpp @@ -0,0 +1,19 @@ +#ifndef CODERS_IMAGEIO_HPP_ +#define CODERS_IMAGEIO_HPP_ + +#include +#include + +class ImageData; + +namespace imageio { + inline const std::string PNG = ".png"; + + bool is_read_supported(const std::string& extension); + bool is_write_supported(const std::string& extension); + + std::unique_ptr read(const std::string& filename); + void write(const std::string& filename, const ImageData* image); +} + +#endif // CODERS_IMAGEIO_HPP_ diff --git a/src/coders/json.cpp b/src/coders/json.cpp index 31440b7d..1bb64776 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -1,13 +1,13 @@ -#include "json.h" +#include "json.hpp" + +#include "../data/dynamic.hpp" +#include "../util/stringutil.hpp" #include #include #include #include -#include "commons.h" -#include "../data/dynamic.h" - using namespace json; using namespace dynamic; @@ -42,10 +42,11 @@ void stringify(const Value* value, const std::string& indentstr, bool nice) { if (value->type == valtype::map) { - stringifyObj(value->value.map, ss, indent, indentstr, nice); + auto map = std::get(value->value); + stringifyObj(map, ss, indent, indentstr, nice); } else if (value->type == valtype::list) { - auto list = value->value.list; + auto list = std::get(value->value); if (list->size() == 0) { ss << "[]"; return; @@ -66,14 +67,14 @@ void stringify(const Value* value, } ss << ']'; } else if (value->type == valtype::boolean) { - ss << (value->value.boolean ? "true" : "false"); + ss << (std::get(value->value) ? "true" : "false"); } else if (value->type == valtype::number) { ss << std::setprecision(15); - ss << value->value.decimal; + ss << std::get(value->value); } else if (value->type == valtype::integer) { - ss << value->value.integer; + ss << std::get(value->value); } else if (value->type == valtype::string) { - ss << escape_string(*value->value.str); + ss << util::escape(std::get(value->value)); } } @@ -94,7 +95,7 @@ void stringifyObj(const Map* obj, newline(ss, nice, indent, indentstr); } Value* value = entry.second.get(); - ss << escape_string(key) << ": "; + ss << util::escape(key) << ": "; stringify(value, ss, indent+1, indentstr, nice); index++; if (index < obj->values.size()) { @@ -116,8 +117,8 @@ std::string json::stringify( return ss.str(); } -Parser::Parser(std::string filename, std::string source) - : BasicParser(filename, source) { +Parser::Parser(const std::string& filename, const std::string& source) + : BasicParser(filename, source) { } Map* Parser::parse() { @@ -189,10 +190,10 @@ Value* Parser::parseValue() { number_u num; valtype type; if (parseNumber(next == '-' ? -1 : 1, num)) { - val.integer = num.ival; + val = std::get(num); type = valtype::integer; } else { - val.decimal = num.fval; + val = std::get(num); type = valtype::number; } return new Value(type, val); @@ -200,53 +201,53 @@ Value* Parser::parseValue() { if (is_identifier_start(next)) { std::string literal = parseName(); if (literal == "true") { - val.boolean = true; + val = true; return new Value(valtype::boolean, val); } else if (literal == "false") { - val.boolean = false; + val = false; return new Value(valtype::boolean, val); } else if (literal == "inf") { - val.decimal = INFINITY; + val = INFINITY; return new Value(valtype::number, val); } else if (literal == "nan") { - val.decimal = NAN; + val = NAN; return new Value(valtype::number, val); } throw error("invalid literal "); } if (next == '{') { - val.map = parseObject(); + val = parseObject(); return new Value(valtype::map, val); } if (next == '[') { - val.list = parseList(); + val = parseList(); return new Value(valtype::list, val); } if (is_digit(next)) { number_u num; valtype type; if (parseNumber(1, num)) { - val.integer = num.ival; + val = std::get(num); type = valtype::integer; } else { - val.decimal = num.fval; + val = std::get(num); type = valtype::number; } return new Value(type, val); } if (next == '"' || next == '\'') { pos++; - val.str = new std::string(parseString(next)); + val = parseString(next); return new Value(valtype::string, val); } throw error("unexpected character '"+std::string({next})+"'"); } -std::unique_ptr json::parse(std::string filename, std::string source) { +std::unique_ptr json::parse(const std::string& filename, const std::string& source) { Parser parser(filename, source); return std::unique_ptr(parser.parse()); } -std::unique_ptr json::parse(std::string source) { +std::unique_ptr json::parse(const std::string& source) { return parse("", source); } diff --git a/src/coders/json.h b/src/coders/json.hpp similarity index 54% rename from src/coders/json.h rename to src/coders/json.hpp index 3fc917da..95b3a942 100644 --- a/src/coders/json.h +++ b/src/coders/json.hpp @@ -1,5 +1,10 @@ -#ifndef CODERS_JSON_H_ -#define CODERS_JSON_H_ +#ifndef CODERS_JSON_HPP_ +#define CODERS_JSON_HPP_ + +#include "commons.hpp" +#include "binary_json.hpp" + +#include "../typedefs.hpp" #include #include @@ -7,11 +12,6 @@ #include #include -#include "commons.h" -#include "../typedefs.h" - -#include "binary_json.h" - namespace dynamic { class Map; class List; @@ -24,13 +24,13 @@ namespace json { dynamic::Map* parseObject(); dynamic::Value* parseValue(); public: - Parser(std::string filename, std::string source); + Parser(const std::string& filename, const std::string& source); dynamic::Map* parse(); }; - extern std::unique_ptr parse(std::string filename, std::string source); - extern std::unique_ptr parse(std::string source); + extern std::unique_ptr parse(const std::string& filename, const std::string& source); + extern std::unique_ptr parse(const std::string& source); extern std::string stringify( const dynamic::Map* obj, @@ -38,4 +38,4 @@ namespace json { const std::string& indent); } -#endif // CODERS_JSON_H_ \ No newline at end of file +#endif // CODERS_JSON_HPP_ diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index c549b61a..f2fd629f 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -1,12 +1,14 @@ -#include "ogg.h" +#include "ogg.hpp" + +#include "../debug/Logger.hpp" +#include "../audio/audio.hpp" +#include "../typedefs.hpp" #include -#include #include #include -#include "../audio/audio.h" -#include "../typedefs.h" +static debug::Logger logger("ogg"); using namespace audio; @@ -56,7 +58,7 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) { if (ret == 0) { eof = true; } else if (ret < 0) { - std::cerr << "ogg::load_pcm: " << vorbis_error_message(ret) << std::endl; + logger.error() << "ogg::load_pcm: " << vorbis_error_message(ret); } else { data.insert(data.end(), std::begin(buffer), std::begin(buffer)+ret); } @@ -98,7 +100,7 @@ public: int bitstream = 0; long bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); if (bytes < 0) { - std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl; + logger.error() << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes; return PCMStream::ERROR; } return bytes; diff --git a/src/coders/ogg.h b/src/coders/ogg.hpp similarity index 74% rename from src/coders/ogg.h rename to src/coders/ogg.hpp index 828f2be5..fd0b69ce 100644 --- a/src/coders/ogg.h +++ b/src/coders/ogg.hpp @@ -1,5 +1,5 @@ -#ifndef CODERS_OGG_H_ -#define CODERS_OGG_H_ +#ifndef CODERS_OGG_HPP_ +#define CODERS_OGG_HPP_ #include @@ -13,4 +13,4 @@ namespace ogg { extern audio::PCMStream* create_stream(const std::filesystem::path& file); } -#endif // CODERS_OGG_H_ +#endif // CODERS_OGG_HPP_ diff --git a/src/coders/png.cpp b/src/coders/png.cpp index 9ac44525..b3128fc8 100644 --- a/src/coders/png.cpp +++ b/src/coders/png.cpp @@ -1,12 +1,14 @@ -#include "png.h" +#include "png.hpp" + +#include "../graphics/core/ImageData.hpp" +#include "../graphics/core/Texture.hpp" +#include "../files/files.hpp" +#include "../debug/Logger.hpp" #include -#include #include -#include "../graphics/ImageData.h" -#include "../graphics/Texture.h" -#include "../files/files.h" +static debug::Logger logger("png-coder"); #ifndef _WIN32 #define LIBPNG @@ -19,78 +21,76 @@ int _png_write(const char* filename, uint width, uint height, const ubyte* data, bool alpha) { uint pixsize = alpha ? 4 : 3; - // Open file for writing (binary mode) - FILE* fp = fopen(filename, "wb"); - if (fp == nullptr) { - fprintf(stderr, "Could not open file %s for writing\n", filename); - fclose(fp); - return 1; - } + // Open file for writing (binary mode) + FILE* fp = fopen(filename, "wb"); + if (fp == nullptr) { + logger.error() << "could not open file " << filename << " for writing"; + fclose(fp); + return 1; + } - // Initialize write structure - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) { - fprintf(stderr, "Could not allocate write struct\n"); - fclose(fp); - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return 1; - } + // Initialize write structure + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png_ptr == nullptr) { + logger.error() << "could not allocate write struct"; + fclose(fp); + return 1; + } - // Initialize info structure - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) { - fprintf(stderr, "Could not allocate info struct\n"); - fclose(fp); - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return 1; - - } + // Initialize info structure + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + logger.error() << "could not allocate info struct"; + fclose(fp); + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); + return 1; + + } - // Setup Exception handling - if (setjmp(png_jmpbuf(png_ptr))) { - fprintf(stderr, "Error during png creation\n"); - fclose(fp); - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return 1; - } + // Setup Exception handling + if (setjmp(png_jmpbuf(png_ptr))) { + logger.error() << "error during png creation"; + fclose(fp); + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } - png_init_io(png_ptr, fp); + png_init_io(png_ptr, fp); - // Write header (8 bit colour depth) - png_set_IHDR(png_ptr, info_ptr, width, height, - 8, + // Write header (8 bit colour depth) + png_set_IHDR(png_ptr, info_ptr, width, height, + 8, alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - png_write_info(png_ptr, info_ptr); + png_write_info(png_ptr, info_ptr); - std::unique_ptr row(new png_byte[pixsize * width]); - - // Write image data - for (uint y = 0; y < height; y++) { - for (uint x = 0; x < width; x++) { + auto row = std::make_unique(pixsize * width); + // Write image data + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { for (uint i = 0; i < pixsize; i++) { row[x * pixsize + i] = (png_byte)data[(y * width + x) * pixsize + i]; } - } - png_write_row(png_ptr, row.get()); - } + } + png_write_row(png_ptr, row.get()); + } - // End write - png_write_end(png_ptr, nullptr); + // End write + png_write_end(png_ptr, nullptr); - fclose(fp); - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_write_struct(&png_ptr, (png_infopp)nullptr); - return 0; + fclose(fp); + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; } -ImageData* _png_load(const char* file){ +std::unique_ptr _png_load(const char* file){ FILE* fp = nullptr; if ((fp = fopen(file, "rb")) == nullptr) { return nullptr; @@ -175,12 +175,12 @@ ImageData* _png_load(const char* file){ format = ImageFormat::rgb888; break; default: - printf("Color type %d not supported!\n", color_type); + logger.error() << "color type " << color_type << " is not supported!"; png_destroy_read_struct(&png, &info, &end_info); - fclose(fp); + fclose(fp); return nullptr; } - ImageData* image = new ImageData(format, width, height, (void*)image_data.release()); + auto image = std::make_unique(format, width, height, (void*)image_data.release()); png_destroy_read_struct(&png, &info, &end_info); fclose(fp); return image; @@ -194,172 +194,159 @@ ImageData* _png_load(const char* file){ static const int SPNG_SUCCESS = 0; //returns spng result code int _png_write(const char* filename, uint width, uint height, const ubyte* data, bool alpha) { - int fmt; - int ret = 0; - spng_ctx* ctx = nullptr; - spng_ihdr ihdr = { 0 }; - uint pixsize = alpha ? 4 : 3; + int fmt; + int ret = 0; + spng_ctx* ctx = nullptr; + spng_ihdr ihdr = { 0 }; + uint pixsize = alpha ? 4 : 3; - ctx = spng_ctx_new(SPNG_CTX_ENCODER); - spng_set_option(ctx, SPNG_ENCODE_TO_BUFFER, 1); + ctx = spng_ctx_new(SPNG_CTX_ENCODER); + spng_set_option(ctx, SPNG_ENCODE_TO_BUFFER, 1); - ihdr.width = width; - ihdr.height = height; - ihdr.color_type = alpha ? SPNG_COLOR_TYPE_TRUECOLOR_ALPHA : SPNG_COLOR_TYPE_TRUECOLOR; - ihdr.bit_depth = 8; + ihdr.width = width; + ihdr.height = height; + ihdr.color_type = alpha ? SPNG_COLOR_TYPE_TRUECOLOR_ALPHA : SPNG_COLOR_TYPE_TRUECOLOR; + ihdr.bit_depth = 8; - spng_set_ihdr(ctx, &ihdr); - fmt = SPNG_FMT_PNG; - ret = spng_encode_image(ctx, data, (size_t)width * (size_t)height * pixsize , fmt, SPNG_ENCODE_FINALIZE); - if (ret != SPNG_SUCCESS) { - printf("spng_encode_image() error: %s\n", spng_strerror(ret)); - fflush(stdout); - spng_ctx_free(ctx); - return ret; - } + spng_set_ihdr(ctx, &ihdr); + fmt = SPNG_FMT_PNG; + ret = spng_encode_image(ctx, data, (size_t)width * (size_t)height * pixsize , fmt, SPNG_ENCODE_FINALIZE); + if (ret != SPNG_SUCCESS) { + logger.error() << "spng_encode_image() error: " << spng_strerror(ret); + spng_ctx_free(ctx); + return ret; + } - size_t png_size; - void* png_buf = spng_get_png_buffer(ctx, &png_size, &ret); + size_t png_size; + void* png_buf = spng_get_png_buffer(ctx, &png_size, &ret); - if (png_buf == nullptr) { - printf("spng_get_png_buffer() error: %s\n", spng_strerror(ret)); - } - else { - files::write_bytes(filename, (const unsigned char*)png_buf, png_size); - } - fflush(stdout); - spng_ctx_free(ctx); - return ret; + if (png_buf == nullptr) { + logger.error() << "spng_get_png_buffer() error: " << spng_strerror(ret); + } + else { + files::write_bytes(filename, (const unsigned char*)png_buf, png_size); + } + spng_ctx_free(ctx); + return ret; } -ImageData* _png_load(const char* file){ - int r = 0; - FILE *png = nullptr; - char *pngbuf = nullptr; - spng_ctx *ctx = nullptr; - unsigned char *out = nullptr; +std::unique_ptr _png_load(const char* file){ + int r = 0; + FILE *png = nullptr; + char *pngbuf = nullptr; + spng_ctx *ctx = nullptr; + unsigned char *out = nullptr; - png = fopen(file, "rb"); - if (png == nullptr){ - std::cerr << "could not to open file " << file << std::endl; - return nullptr; - } + png = fopen(file, "rb"); + if (png == nullptr){ + logger.error() << "could not to open file " << file; + return nullptr; + } - fseek(png, 0, SEEK_END); - long siz_pngbuf = ftell(png); - rewind(png); - if(siz_pngbuf < 1) { - fclose(png); - std::cerr << "could not to read file " << file << std::endl; - return nullptr; - } - pngbuf = new char[siz_pngbuf]; - if(fread(pngbuf, siz_pngbuf, 1, png) != 1){ //check of read elements count - fclose(png); - delete[] pngbuf; - std::cerr << "fread() failed" << std::endl; - return nullptr; - } - fclose(png); // <- finally closing file - ctx = spng_ctx_new(0); - if (ctx == nullptr){ - delete[] pngbuf; - std::cerr << "spng_ctx_new() failed" << std::endl; - return nullptr; - } - r = spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); - if (r != SPNG_SUCCESS){ - delete[] pngbuf; - spng_ctx_free(ctx); - std::cerr << "spng_set_crc_action() error: " << spng_strerror(r) << std::endl; - return nullptr; - } - r = spng_set_png_buffer(ctx, pngbuf, siz_pngbuf); - if (r != SPNG_SUCCESS){ - delete[] pngbuf; - spng_ctx_free(ctx); - std::cerr << "spng_set_png_buffer() error: " << spng_strerror(r) << std::endl; - return nullptr; - } + fseek(png, 0, SEEK_END); + long siz_pngbuf = ftell(png); + rewind(png); + if(siz_pngbuf < 1) { + fclose(png); + logger.error() << "could not to read file " << file; + return nullptr; + } + pngbuf = new char[siz_pngbuf]; + if(fread(pngbuf, siz_pngbuf, 1, png) != 1){ //check of read elements count + fclose(png); + delete[] pngbuf; + logger.error() << "fread() failed: " << file; + return nullptr; + } + fclose(png); // <- finally closing file + ctx = spng_ctx_new(0); + if (ctx == nullptr){ + delete[] pngbuf; + logger.error() << "spng_ctx_new() failed"; + return nullptr; + } + r = spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); + if (r != SPNG_SUCCESS){ + delete[] pngbuf; + spng_ctx_free(ctx); + logger.error() << "spng_set_crc_action(): " << spng_strerror(r); + return nullptr; + } + r = spng_set_png_buffer(ctx, pngbuf, siz_pngbuf); + if (r != SPNG_SUCCESS){ + delete[] pngbuf; + spng_ctx_free(ctx); + logger.error() << "spng_set_png_buffer(): " << spng_strerror(r); + return nullptr; + } - spng_ihdr ihdr; - r = spng_get_ihdr(ctx, &ihdr); - if (r != SPNG_SUCCESS){ - delete[] pngbuf; - spng_ctx_free(ctx); - std::cerr << "spng_get_ihdr() error: " << spng_strerror(r) << std::endl; - return nullptr; - } - //// Unused "something" - //const char *clr_type_str; - //if(ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE) - // clr_type_str = "grayscale"; - //else if(ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR) - // clr_type_str = "truecolor"; - //else if(ihdr.color_type == SPNG_COLOR_TYPE_INDEXED) - // clr_type_str = "indexed color"; - //else if(ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) - // clr_type_str = "grayscale with alpha"; - //else - // clr_type_str = "truecolor with alpha"; + spng_ihdr ihdr; + r = spng_get_ihdr(ctx, &ihdr); + if (r != SPNG_SUCCESS){ + delete[] pngbuf; + spng_ctx_free(ctx); + logger.error() << "spng_get_ihdr(): " << spng_strerror(r); + return nullptr; + } - size_t out_size; - r = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size); - if (r != SPNG_SUCCESS){ - delete[] pngbuf; - spng_ctx_free(ctx); - std::cerr << "spng_decoded_image_size() error: " << spng_strerror(r) << std::endl; - return nullptr; - } - out = new unsigned char[out_size]; - r = spng_decode_image(ctx, out, out_size, SPNG_FMT_RGBA8, 0); - if (r != SPNG_SUCCESS){ - delete[] out; - delete[] pngbuf; - spng_ctx_free(ctx); - std::cerr << "spng_decode_image() error: " << spng_strerror(r) << std::endl; - return nullptr; - } + size_t out_size; + r = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &out_size); + if (r != SPNG_SUCCESS){ + delete[] pngbuf; + spng_ctx_free(ctx); + logger.error() << "spng_decoded_image_size(): " << spng_strerror(r); + return nullptr; + } + out = new unsigned char[out_size]; + r = spng_decode_image(ctx, out, out_size, SPNG_FMT_RGBA8, 0); + if (r != SPNG_SUCCESS){ + delete[] out; + delete[] pngbuf; + spng_ctx_free(ctx); + logger.error() << "spng_decode_image(): " << spng_strerror(r); + return nullptr; + } - unsigned char* flipped = new unsigned char[out_size]; + unsigned char* flipped = new unsigned char[out_size]; - for (size_t i = 0; i < ihdr.height; i+=1){ - size_t rowsize = ihdr.width*4; - for (size_t j = 0; j < rowsize; j++){ - flipped[(ihdr.height-i-1)*rowsize+j] = out[i*rowsize+j]; - } - } - delete[] out; // <- finally delete out // no, delete spng usage + for (size_t i = 0; i < ihdr.height; i+=1){ + size_t rowsize = ihdr.width*4; + for (size_t j = 0; j < rowsize; j++){ + flipped[(ihdr.height-i-1)*rowsize+j] = out[i*rowsize+j]; + } + } + delete[] out; // <- finally delete out // no, delete spng usage - ImageData* image = new ImageData(ImageFormat::rgba8888, ihdr.width, ihdr.height, (void*)flipped); + auto image = std::make_unique(ImageFormat::rgba8888, ihdr.width, ihdr.height, (void*)flipped); - delete[] pngbuf; - spng_ctx_free(ctx); + delete[] pngbuf; + spng_ctx_free(ctx); return image; } #endif -ImageData* png::load_image(std::string filename) { - ImageData* image (_png_load(filename.c_str())); - if (image == nullptr) { - std::cerr << "Could not load image " << filename << std::endl; - return nullptr; - } +std::unique_ptr png::load_image(const std::string& filename) { + auto image = _png_load(filename.c_str()); + if (image == nullptr) { + throw std::runtime_error("could not load image "+filename); + } return image; } -Texture* png::load_texture(std::string filename) { - std::unique_ptr image (_png_load(filename.c_str())); - if (image == nullptr){ - std::cerr << "Could not load texture " << filename << std::endl; - return nullptr; - } - auto texture = Texture::from(image.get()); +std::unique_ptr png::load_texture(const std::string& filename) { + auto image = load_image(filename); + auto texture = Texture::from(image.get()); texture->setNearestFilter(); return texture; } -void png::write_image(std::string filename, const ImageData* image) { - _png_write(filename.c_str(), image->getWidth(), image->getHeight(), (const ubyte*)image->getData(), image->getFormat() == ImageFormat::rgba8888); -} \ No newline at end of file +void png::write_image(const std::string& filename, const ImageData* image) { + _png_write( + filename.c_str(), + image->getWidth(), + image->getHeight(), + (const ubyte*)image->getData(), + image->getFormat() == ImageFormat::rgba8888 + ); +} diff --git a/src/coders/png.h b/src/coders/png.h deleted file mode 100644 index f824a3c7..00000000 --- a/src/coders/png.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CODERS_PNG_H_ -#define CODERS_PNG_H_ - -#include -#include "../typedefs.h" - -class Texture; -class ImageData; - -namespace png { - extern ImageData* load_image(std::string filename); - extern void write_image(std::string filename, const ImageData* image); - extern Texture* load_texture(std::string filename); -} - -#endif /* CODERS_PNG_H_ */ diff --git a/src/coders/png.hpp b/src/coders/png.hpp new file mode 100644 index 00000000..59af39e5 --- /dev/null +++ b/src/coders/png.hpp @@ -0,0 +1,16 @@ +#ifndef CODERS_PNG_HPP_ +#define CODERS_PNG_HPP_ + +#include +#include + +class Texture; +class ImageData; + +namespace png { + std::unique_ptr load_image(const std::string& filename); + void write_image(const std::string& filename, const ImageData* image); + std::unique_ptr load_texture(const std::string& filename); +} + +#endif // CODERS_PNG_HPP_ diff --git a/src/coders/rle.cpp b/src/coders/rle.cpp new file mode 100644 index 00000000..8acb5b8b --- /dev/null +++ b/src/coders/rle.cpp @@ -0,0 +1,90 @@ +#include "rle.hpp" + +size_t rle::decode(const ubyte* src, size_t srclen, ubyte* dst) { + size_t offset = 0; + for (size_t i = 0; i < srclen;) { + ubyte len = src[i++]; + ubyte c = src[i++]; + for (size_t j = 0; j <= len; j++) { + dst[offset++] = c; + } + } + return offset; +} + +size_t rle::encode(const ubyte* src, size_t srclen, ubyte* dst) { + if (srclen == 0) { + return 0; + } + size_t offset = 0; + ubyte counter = 0; + ubyte c = src[0]; + for (size_t i = 1; i < srclen; i++) { + ubyte cnext = src[i]; + if (cnext != c || counter == 255) { + dst[offset++] = counter; + dst[offset++] = c; + c = cnext; + counter = 0; + } + else { + counter++; + } + } + dst[offset++] = counter; + dst[offset++] = c; + return offset; +} + + +size_t extrle::decode(const ubyte* src, size_t srclen, ubyte* dst) { + size_t offset = 0; + for (size_t i = 0; i < srclen;) { + uint len = src[i++]; + if (len & 0x80) { + len &= 0x7F; + len |= ((uint)src[i++]) << 7; + } + ubyte c = src[i++]; + for (size_t j = 0; j <= len; j++) { + dst[offset++] = c; + } + } + return offset; +} + +size_t extrle::encode(const ubyte* src, size_t srclen, ubyte* dst) { + if (srclen == 0) { + return 0; + } + size_t offset = 0; + uint counter = 0; + ubyte c = src[0]; + for (size_t i = 1; i < srclen; i++) { + ubyte cnext = src[i]; + if (cnext != c || counter == max_sequence) { + if (counter >= 0x80) { + dst[offset++] = 0x80 | (counter & 0x7F); + dst[offset++] = counter >> 7; + } + else { + dst[offset++] = counter; + } + dst[offset++] = c; + c = cnext; + counter = 0; + } + else { + counter++; + } + } + if (counter >= 0x80) { + dst[offset++] = 0x80 | (counter & 0x7F); + dst[offset++] = counter >> 7; + } + else { + dst[offset++] = counter; + } + dst[offset++] = c; + return offset; +} diff --git a/src/coders/rle.hpp b/src/coders/rle.hpp new file mode 100644 index 00000000..9055296a --- /dev/null +++ b/src/coders/rle.hpp @@ -0,0 +1,17 @@ +#ifndef CODERS_RLE_HPP_ +#define CODERS_RLE_HPP_ + +#include "../typedefs.hpp" + +namespace rle { + size_t encode(const ubyte* src, size_t length, ubyte* dst); + size_t decode(const ubyte* src, size_t length, ubyte* dst); +} + +namespace extrle { + constexpr uint max_sequence = 0x7FFF; + size_t encode(const ubyte* src, size_t length, ubyte* dst); + size_t decode(const ubyte* src, size_t length, ubyte* dst); +} + +#endif // CODERS_RLE_HPP_ diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index b772cbe0..10139285 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -1,249 +1,135 @@ -#include "toml.h" -#include "commons.h" - -#include -#include -#include -#include -#include - -using std::string; - -using namespace toml; - -Section::Section(string name) : name(name) { -} - -void Section::add(std::string name, Field field) { - if (fields.find(name) != fields.end()) { - throw std::runtime_error("field duplication"); - } - fields[name] = field; - keyOrder.push_back(name); -} - -void Section::add(string name, bool* ptr) { - add(name, {fieldtype::ftbool, ptr}); -} - -void Section::add(string name, int* ptr) { - add(name, {fieldtype::ftint, ptr}); -} - -void Section::add(string name, uint* ptr) { - add(name, {fieldtype::ftuint, ptr}); -} - -void Section::add(string name, float* ptr) { - add(name, {fieldtype::ftfloat, ptr}); -} - -void Section::add(string name, string* ptr) { - add(name, {fieldtype::ftstring, ptr}); -} - -string Section::getName() const { - return name; -} - -const Field* Section::field(std::string name) const { - auto found = fields.find(name); - if (found == fields.end()) { - return nullptr; - } - return &found->second; -} - -const std::vector& Section::keys() const { - return keyOrder; -} - -Wrapper::~Wrapper() { - for (auto entry : sections) { - delete entry.second; - } -} - -Section& Wrapper::add(std::string name) { - if (sections.find(name) != sections.end()) { - throw std::runtime_error("section duplication"); - } - Section* section = new Section(name); - sections[name] = section; - keyOrder.push_back(name); - return *section; -} - -Section* Wrapper::section(std::string name) { - auto found = sections.find(name); - if (found == sections.end()) { - return nullptr; - } - return found->second; -} - -std::string Wrapper::write() const { - std::stringstream ss; - for (string key : keyOrder) { - const Section* section = sections.at(key); - ss << "[" << key << "]\n"; - for (const string& key : section->keys()) { - ss << key << " = "; - const Field* field = section->field(key); - assert(field != nullptr); - switch (field->type) { - case fieldtype::ftbool: - ss << (*((bool*)field->ptr) ? "true" : "false"); - break; - case fieldtype::ftint: ss << *((int*)field->ptr); break; - case fieldtype::ftuint: ss << *((uint*)field->ptr); break; - case fieldtype::ftfloat: ss << *((float*)field->ptr); break; - case fieldtype::ftstring: - ss << escape_string(*((const string*)field->ptr)); - break; - } - ss << "\n"; - } - ss << "\n"; - } - return ss.str(); -} - -Reader::Reader(Wrapper* wrapper, string file, string source) : BasicParser(file, source), wrapper(wrapper) { -} - -void Reader::skipWhitespace() { - BasicParser::skipWhitespace(); - if (hasNext() && source[pos] == '#') { - skipLine(); - if (hasNext() && is_whitespace(peek())) { - skipWhitespace(); - } - } -} - -void Reader::read() { - skipWhitespace(); - if (!hasNext()) { - return; - } - readSection(nullptr); -} - -inline bool is_numeric_type(fieldtype type) { - return type == fieldtype::ftint || type == fieldtype::ftfloat; -} - -void Section::set(string name, double value) { - const Field* field = this->field(name); - if (field == nullptr) { - std::cerr << "warning: unknown key '" << name << "'" << std::endl; - } else { - switch (field->type) { - case fieldtype::ftbool: *(bool*)(field->ptr) = fabs(value) > 0.0; break; - case fieldtype::ftint: *(int*)(field->ptr) = value; break; - case fieldtype::ftuint: *(uint*)(field->ptr) = value; break; - case fieldtype::ftfloat: *(float*)(field->ptr) = value; break; - case fieldtype::ftstring: *(string*)(field->ptr) = std::to_string(value); break; - default: - std::cerr << "error: type error for key '" << name << "'" << std::endl; - } - } -} - -void Section::set(std::string name, bool value) { - const Field* field = this->field(name); - if (field == nullptr) { - std::cerr << "warning: unknown key '" << name << "'" << std::endl; - } else { - switch (field->type) { - case fieldtype::ftbool: *(bool*)(field->ptr) = value; break; - case fieldtype::ftint: *(int*)(field->ptr) = (int)value; break; - case fieldtype::ftuint: *(uint*)(field->ptr) = (uint)value; break; - case fieldtype::ftfloat: *(float*)(field->ptr) = (float)value; break; - case fieldtype::ftstring: *(string*)(field->ptr) = value ? "true" : "false"; break; - default: - std::cerr << "error: type error for key '" << name << "'" << std::endl; - } - } -} - -void Section::set(std::string name, std::string value) { - const Field* field = this->field(name); - if (field == nullptr) { - std::cerr << "warning: unknown key '" << name << "'" << std::endl; - } else { - switch (field->type) { - case fieldtype::ftstring: *(string*)(field->ptr) = value; break; - default: - std::cerr << "error: type error for key '" << name << "'" << std::endl; - } - } -} - -void Reader::readSection(Section* section /*nullable*/) { - while (hasNext()) { - skipWhitespace(); - if (!hasNext()) { - break; - } - char c = nextChar(); - if (c == '[') { - string name = parseName(); - Section* section = wrapper->section(name); - pos++; - readSection(section); - return; - } - pos--; - string name = parseName(); - expect('='); - c = peek(); - if (is_digit(c)) { - number_u num; - if (parseNumber(1, num)) { - if (section) - section->set(name, (double)num.ival); - } else { - if (section) - section->set(name, num.fval); - } - } else if (c == '-' || c == '+') { - int sign = c == '-' ? -1 : 1; - pos++; - number_u num; - if (parseNumber(sign, num)) { - if (section) - section->set(name, (double)num.ival); - } else { - if (section) - section->set(name, num.fval); - } - } else if (is_identifier_start(c)) { - string identifier = parseName(); - if (identifier == "true" || identifier == "false") { - bool flag = identifier == "true"; - if (section) { - section->set(name, flag); - } - } else if (identifier == "inf") { - if (section) { - section->set(name, INFINITY); - } - } else if (identifier == "nan") { - if (section) { - section->set(name, NAN); - } - } - } else if (c == '"' || c == '\'') { - pos++; - string str = parseString(c); - if (section) { - section->set(name, str); - } - } else { - throw error("feature is not supported"); - } - expectNewLine(); - } -} \ No newline at end of file +#include "toml.hpp" + +#include "commons.hpp" +#include "../data/setting.hpp" +#include "../data/dynamic.hpp" +#include "../util/stringutil.hpp" +#include "../files/settings_io.hpp" + +#include +#include +#include +#include +#include + +using namespace toml; + +class Reader : public BasicParser { + SettingsHandler& handler; + + void skipWhitespace() override { + BasicParser::skipWhitespace(); + if (hasNext() && source[pos] == '#') { + skipLine(); + if (hasNext() && is_whitespace(peek())) { + skipWhitespace(); + } + } + } + void readSection(const std::string& section) { + while (hasNext()) { + skipWhitespace(); + if (!hasNext()) { + break; + } + char c = nextChar(); + if (c == '[') { + std::string name = parseName(); + pos++; + readSection(name); + return; + } + pos--; + std::string name = section+"."+parseName(); + expect('='); + c = peek(); + if (is_digit(c)) { + number_u num; + parseNumber(1, num); + if (handler.has(name)) { + handler.setValue(name, *dynamic::Value::of(num)); + } + } else if (c == '-' || c == '+') { + int sign = c == '-' ? -1 : 1; + pos++; + number_u num; + parseNumber(sign, num); + if (handler.has(name)) { + handler.setValue(name, *dynamic::Value::of(num)); + } + } else if (is_identifier_start(c)) { + std::string identifier = parseName(); + if (handler.has(name)) { + if (identifier == "true" || identifier == "false") { + bool flag = identifier == "true"; + handler.setValue(name, *dynamic::Value::boolean(flag)); + } else if (identifier == "inf") { + handler.setValue(name, *dynamic::Value::of(INFINITY)); + } else if (identifier == "nan") { + handler.setValue(name, *dynamic::Value::of(NAN)); + } + } + } else if (c == '"' || c == '\'') { + pos++; + std::string str = parseString(c); + if (handler.has(name)) { + handler.setValue(name, *dynamic::Value::of(str)); + } + } else { + throw error("feature is not supported"); + } + expectNewLine(); + } + } + +public: + Reader( + SettingsHandler& handler, + const std::string& file, + const std::string& source) + : BasicParser(file, source), handler(handler) { + } + + void read() { + skipWhitespace(); + if (!hasNext()) { + return; + } + readSection(""); + } +}; + +void toml::parse( + SettingsHandler& handler, + const std::string& file, + const std::string& source +) { + Reader reader(handler, file, source); + reader.read(); +} + +std::string toml::stringify(SettingsHandler& handler) { + auto& sections = handler.getSections(); + + std::stringstream ss; + for (auto& section : sections) { + ss << "[" << section.name << "]\n"; + for (const std::string& key : section.keys) { + ss << key << " = "; + auto setting = handler.getSetting(section.name+"."+key); + assert(setting != nullptr); + if (auto integer = dynamic_cast(setting)) { + ss << integer->get(); + } else if (auto number = dynamic_cast(setting)) { + ss << number->get(); + } else if (auto flag = dynamic_cast(setting)) { + ss << (flag->get() ? "true" : "false"); + } else if (auto string = dynamic_cast(setting)) { + ss << util::escape(string->get()); + } + ss << "\n"; + } + ss << "\n"; + } + return ss.str(); +} diff --git a/src/coders/toml.h b/src/coders/toml.h deleted file mode 100644 index 80fa85ca..00000000 --- a/src/coders/toml.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CODERS_TOML_H_ -#define CODERS_TOML_H_ - -#include -#include -#include - -#include "commons.h" - -namespace toml { - enum class fieldtype { - ftbool, - ftint, - ftuint, - ftfloat, - ftstring, - }; - - struct Field { - fieldtype type; - void* ptr; - }; - - class Section { - std::unordered_map fields; - std::vector keyOrder; - std::string name; - void add(std::string name, Field field); - public: - Section(std::string name); - void add(std::string name, bool* ptr); - void add(std::string name, int* ptr); - void add(std::string name, uint* ptr); - void add(std::string name, float* ptr); - void add(std::string name, std::string* ptr); - - const Field* field(std::string name) const; - - void set(std::string name, double value); - void set(std::string name, bool value); - void set(std::string name, std::string value); - - std::string getName() const; - const std::vector& keys() const; - }; - - class Wrapper { - std::unordered_map sections; - std::vector keyOrder; - public: - ~Wrapper(); - Section& add(std::string section); - Section* section(std::string name); - - std::string write() const; - }; - - class Reader : public BasicParser { - Wrapper* wrapper; - void skipWhitespace() override; - void readSection(Section* section); - public: - Reader(Wrapper* wrapper, std::string file, std::string source); - void read(); - }; -} - -#endif // CODERS_TOML_H_ \ No newline at end of file diff --git a/src/coders/toml.hpp b/src/coders/toml.hpp new file mode 100644 index 00000000..4574e468 --- /dev/null +++ b/src/coders/toml.hpp @@ -0,0 +1,20 @@ +#ifndef CODERS_TOML_HPP_ +#define CODERS_TOML_HPP_ + +#include "commons.hpp" + +#include + +class SettingsHandler; + +namespace toml { + std::string stringify(SettingsHandler& handler); + + void parse( + SettingsHandler& handler, + const std::string& file, + const std::string& source + ); +} + +#endif // CODERS_TOML_HPP_ diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index 19a4233b..ac45ee32 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -1,4 +1,6 @@ -#include "wav.h" +#include "wav.hpp" + +#include "../audio/audio.hpp" #include #include @@ -7,8 +9,6 @@ #include #include -#include "../audio/audio.h" - bool is_big_endian() { uint32_t ui32_v = 0x01020304; char bytes[sizeof(uint32_t)]; diff --git a/src/coders/wav.h b/src/coders/wav.hpp similarity index 78% rename from src/coders/wav.h rename to src/coders/wav.hpp index 5a8a2095..be648535 100644 --- a/src/coders/wav.h +++ b/src/coders/wav.hpp @@ -1,5 +1,5 @@ -#ifndef CODERS_WAV_H_ -#define CODERS_WAV_H_ +#ifndef CODERS_WAV_HPP_ +#define CODERS_WAV_HPP_ #include @@ -13,4 +13,4 @@ namespace wav { extern audio::PCMStream* create_stream(const std::filesystem::path& file); } -#endif // CODERS_WAV_H_ +#endif // CODERS_WAV_HPP_ diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp index 03b4b3cd..59842ff2 100644 --- a/src/coders/xml.cpp +++ b/src/coders/xml.cpp @@ -1,9 +1,10 @@ -#include "xml.h" +#include "xml.hpp" + +#include "../util/stringutil.hpp" #include #include #include -#include "../util/stringutil.h" using namespace xml; @@ -36,7 +37,7 @@ bool Attribute::asBool() const { glm::vec2 Attribute::asVec2() const { size_t pos = text.find(','); if (pos == std::string::npos) { - throw std::runtime_error("invalid vec2 value "+escape_string(text)); + return glm::vec2(util::parse_double(text, 0, text.length())); } return glm::vec2( util::parse_double(text, 0, pos), @@ -48,11 +49,11 @@ glm::vec2 Attribute::asVec2() const { glm::vec3 Attribute::asVec3() const { size_t pos1 = text.find(','); if (pos1 == std::string::npos) { - throw std::runtime_error("invalid vec3 value "+escape_string(text)); + return glm::vec3(util::parse_double(text, 0, text.length())); } size_t pos2 = text.find(',', pos1+1); if (pos2 == std::string::npos) { - throw std::runtime_error("invalid vec3 value "+escape_string(text)); + throw std::runtime_error("invalid vec3 value "+util::quote(text)); } return glm::vec3( util::parse_double(text, 0, pos1), @@ -65,15 +66,15 @@ glm::vec3 Attribute::asVec3() const { glm::vec4 Attribute::asVec4() const { size_t pos1 = text.find(','); if (pos1 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(text)); + return glm::vec4(util::parse_double(text, 0, text.length())); } size_t pos2 = text.find(',', pos1+1); if (pos2 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(text)); + throw std::runtime_error("invalid vec4 value "+util::quote(text)); } size_t pos3 = text.find(',', pos2+1); if (pos3 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(text)); + throw std::runtime_error("invalid vec4 value "+util::quote(text)); } return glm::vec4( util::parse_double(text, 0, pos1), @@ -99,7 +100,7 @@ glm::vec4 Attribute::asColor() const { } return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f); } else { - throw std::runtime_error("hex colors are only supported"); + return asVec4() / 255.f; } } @@ -176,7 +177,7 @@ const std::string& Document::getEncoding() const { return encoding; } -Parser::Parser(std::string filename, std::string source) +Parser::Parser(const std::string& filename, const std::string& source) : BasicParser(filename, source) { } @@ -196,8 +197,13 @@ xmlelement Parser::parseOpenTag() { if (peek() == '=') { nextChar(); skipWhitespace(); - expect('"'); - attrtext = parseString('"'); + + char quote = peek(); + if (quote != '\'' && quote != '"') { + throw error("string literal expected"); + } + skip(1); + attrtext = parseString(quote); } node->set(attrname, attrtext); } @@ -377,7 +383,7 @@ static void stringifyElement( auto attr = entry.second; ss << attr.getName(); if (!attr.getText().empty()) { - ss << "=" << escape_string(attr.getText()); + ss << "=" << util::escape(attr.getText()); } if (count + 1 < int(attrs.size())) { ss << " "; diff --git a/src/coders/xml.h b/src/coders/xml.hpp similarity index 93% rename from src/coders/xml.h rename to src/coders/xml.hpp index 21ee96d9..f8da875d 100644 --- a/src/coders/xml.h +++ b/src/coders/xml.hpp @@ -1,5 +1,7 @@ -#ifndef CODERS_XML_H_ -#define CODERS_XML_H_ +#ifndef CODERS_XML_HPP_ +#define CODERS_XML_HPP_ + +#include "commons.hpp" #include #include @@ -7,8 +9,6 @@ #include #include -#include "commons.h" - namespace xml { class Node; class Attribute; @@ -118,7 +118,7 @@ namespace xml { std::string parseText(); std::string parseXMLName(); public: - Parser(std::string filename, std::string source); + Parser(const std::string& filename, const std::string& source); xmldocument parse(); }; @@ -141,4 +141,4 @@ namespace xml { extern xmldocument parse(std::string filename, std::string source); } -#endif // CODERS_XML_H_ +#endif // CODERS_XML_HPP_ diff --git a/src/constants.h b/src/constants.hpp similarity index 66% rename from src/constants.h rename to src/constants.hpp index 8edca7d6..202d7ac5 100644 --- a/src/constants.h +++ b/src/constants.hpp @@ -1,13 +1,20 @@ -#ifndef SRC_CONSTANTS_H_ -#define SRC_CONSTANTS_H_ +#ifndef CONSTANTS_HPP_ +#define CONSTANTS_HPP_ + +#include "typedefs.hpp" #include #include -#include "typedefs.h" inline constexpr int ENGINE_VERSION_MAJOR = 0; inline constexpr int ENGINE_VERSION_MINOR = 21; -inline constexpr bool ENGINE_VERSION_INDEV = true; + +#ifdef NDEBUG +inline constexpr bool ENGINE_DEBUG_BUILD = false; +#else +inline constexpr bool ENGINE_DEBUG_BUILD = true; +#endif // NDEBUG + inline const std::string ENGINE_VERSION_STRING = "0.21"; inline constexpr int BLOCK_AIR = 0; @@ -20,19 +27,21 @@ inline constexpr int CHUNK_D = 16; inline constexpr uint VOXEL_USER_BITS = 8; inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS; +/// @brief pixel size of an item inventory icon inline constexpr int ITEM_ICON_SIZE = 48; -/* Chunk volume (count of voxels per Chunk) */ +/// @brief chunk volume (count of voxels per Chunk) inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D); -/* BLOCK_VOID is block id used to mark non-existing voxel (voxel of missing chunk) */ +/// @brief block id used to mark non-existing voxel (voxel of missing chunk) inline constexpr blockid_t BLOCK_VOID = std::numeric_limits::max(); +/// @brief item id used to mark non-existing item (error) inline constexpr itemid_t ITEM_VOID = std::numeric_limits::max(); - +/// @brief max number of block definitions possible inline constexpr blockid_t MAX_BLOCKS = BLOCK_VOID; inline constexpr uint vox_index(uint x, uint y, uint z, uint w=CHUNK_W, uint d=CHUNK_D) { - return (y * d + z) * w + x; + return (y * d + z) * w + x; } inline const std::string SHADERS_FOLDER = "shaders"; @@ -41,4 +50,4 @@ inline const std::string FONTS_FOLDER = "fonts"; inline const std::string LAYOUTS_FOLDER = "layouts"; inline const std::string SOUNDS_FOLDER = "sounds"; -#endif // SRC_CONSTANTS_H_ +#endif // CONSTANTS_HPP_ diff --git a/src/content/Content.cpp b/src/content/Content.cpp index 2652a4a5..df9e4d3f 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -1,14 +1,14 @@ -#include "Content.h" +#include "Content.hpp" #include #include #include -#include "../voxels/Block.h" -#include "../items/ItemDef.h" +#include "../voxels/Block.hpp" +#include "../items/ItemDef.hpp" -#include "ContentPack.h" -#include "../logic/scripting/scripting.h" +#include "ContentPack.hpp" +#include "../logic/scripting/scripting.hpp" ContentBuilder::~ContentBuilder() {} @@ -155,7 +155,14 @@ Content::Content( drawGroups(std::move(drawGroups)) {} -Content::~Content() {} +Content::~Content() { + for (auto& entry : blockDefs) { + delete entry.second; + } + for (auto& entry : itemDefs) { + delete entry.second; + } +} Block* Content::findBlock(std::string id) const { auto found = blockDefs.find(id); diff --git a/src/content/Content.h b/src/content/Content.hpp similarity index 92% rename from src/content/Content.h rename to src/content/Content.hpp index 1f921010..dcf3c56f 100644 --- a/src/content/Content.h +++ b/src/content/Content.hpp @@ -1,5 +1,5 @@ -#ifndef CONTENT_CONTENT_H_ -#define CONTENT_CONTENT_H_ +#ifndef CONTENT_CONTENT_HPP_ +#define CONTENT_CONTENT_HPP_ #include #include @@ -7,8 +7,8 @@ #include #include #include -#include "../typedefs.h" -#include "../voxels/Block.h" +#include "../typedefs.hpp" +#include "../voxels/Block.hpp" using DrawGroups = std::set; @@ -146,4 +146,4 @@ public: const std::unordered_map>& getPacks() const; }; -#endif // CONTENT_CONTENT_H_ +#endif // CONTENT_CONTENT_HPP_ diff --git a/src/content/ContentLUT.cpp b/src/content/ContentLUT.cpp index e27ef1da..6e76642c 100644 --- a/src/content/ContentLUT.cpp +++ b/src/content/ContentLUT.cpp @@ -1,16 +1,15 @@ -#include "ContentLUT.h" +#include "ContentLUT.hpp" + +#include "Content.hpp" +#include "../constants.hpp" +#include "../files/files.hpp" +#include "../coders/json.hpp" +#include "../voxels/Block.hpp" +#include "../items/ItemDef.hpp" +#include "../data/dynamic.hpp" #include -#include "Content.h" -#include "../constants.h" -#include "../files/files.h" -#include "../coders/json.h" -#include "../voxels/Block.h" -#include "../items/ItemDef.h" - -#include "../data/dynamic.h" - ContentLUT::ContentLUT(const Content* content, size_t blocksCount, size_t itemsCount) { auto* indices = content->getIndices(); for (size_t i = 0; i < blocksCount; i++) { diff --git a/src/content/ContentLUT.h b/src/content/ContentLUT.hpp similarity index 88% rename from src/content/ContentLUT.h rename to src/content/ContentLUT.hpp index 8b3f5886..399ad36f 100644 --- a/src/content/ContentLUT.h +++ b/src/content/ContentLUT.hpp @@ -1,15 +1,15 @@ -#ifndef CONTENT_CONTENT_LUT_H_ -#define CONTENT_CONTENT_LUT_H_ +#ifndef CONTENT_CONTENT_LUT_HPP_ +#define CONTENT_CONTENT_LUT_HPP_ + +#include "Content.hpp" + +#include "../typedefs.hpp" +#include "../constants.hpp" #include #include #include -#include "../typedefs.h" -#include "../constants.h" - -#include "Content.h" - namespace fs = std::filesystem; struct contententry { @@ -93,4 +93,4 @@ public: std::vector getMissingContent() const; }; -#endif // CONTENT_CONTENT_LUT_H_ +#endif // CONTENT_CONTENT_LUT_HPP_ diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 3da09e47..f6c53242 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -1,4 +1,17 @@ -#include "ContentLoader.h" +#include "ContentLoader.hpp" + +#include "Content.hpp" +#include "ContentPack.hpp" +#include "../coders/json.hpp" +#include "../core_defs.hpp" +#include "../data/dynamic.hpp" +#include "../debug/Logger.hpp" +#include "../files/files.hpp" +#include "../items/ItemDef.hpp" +#include "../logic/scripting/scripting.hpp" +#include "../typedefs.hpp" +#include "../util/listutil.hpp" +#include "../voxels/Block.hpp" #include #include @@ -6,21 +19,10 @@ #include #include -#include "Content.h" -#include "../items/ItemDef.h" -#include "../util/listutil.h" -#include "../voxels/Block.h" -#include "../files/files.h" -#include "../coders/json.h" -#include "../typedefs.h" -#include "../core_defs.h" -#include "../data/dynamic.h" - -#include "ContentPack.h" -#include "../logic/scripting/scripting.h" - namespace fs = std::filesystem; +static debug::Logger logger("content-loader"); + ContentLoader::ContentLoader(ContentPack* pack) : pack(pack) { } @@ -100,7 +102,6 @@ void ContentLoader::fixPackIndices() { if (modified){ // rewrite modified json - std::cout << indexFile << std::endl; files::write_json(indexFile, root.get()); } } @@ -321,11 +322,11 @@ BlockMaterial ContentLoader::loadBlockMaterial(fs::path file, std::string full) } void ContentLoader::load(ContentBuilder& builder) { - std::cout << "-- loading pack [" << pack->id << "]" << std::endl; + logger.info() << "loading pack [" << pack->id << "]"; auto runtime = new ContentPackRuntime(*pack, scripting::create_pack_environment(*pack)); builder.add(runtime); - env = runtime->getEnvironment()->getId(); + env = runtime->getEnvironment(); ContentPackStats& stats = runtime->getStatsWriteable(); fixPackIndices(); diff --git a/src/content/ContentLoader.h b/src/content/ContentLoader.hpp similarity index 81% rename from src/content/ContentLoader.h rename to src/content/ContentLoader.hpp index b176b861..d709f0d0 100644 --- a/src/content/ContentLoader.h +++ b/src/content/ContentLoader.hpp @@ -1,7 +1,7 @@ -#ifndef CONTENT_CONTENT_LOADER_H_ -#define CONTENT_CONTENT_LOADER_H_ +#ifndef CONTENT_CONTENT_LOADER_HPP_ +#define CONTENT_CONTENT_LOADER_HPP_ -#include "../voxels/Block.h" +#include "../voxels/Block.hpp" #include #include @@ -18,7 +18,7 @@ namespace dynamic { class ContentLoader { const ContentPack* pack; - int env = 0; + scriptenv env; void loadBlock(Block& def, std::string full, std::string name); void loadCustomBlockModel(Block& def, dynamic::Map* primitives); @@ -38,4 +38,4 @@ public: void load(ContentBuilder& builder); }; -#endif // CONTENT_CONTENT_LOADER_H_ +#endif // CONTENT_CONTENT_LOADER_HPP_ diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 5ad8d9ba..a054e426 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -1,13 +1,12 @@ -#include "ContentPack.h" +#include "ContentPack.hpp" #include #include -#include "../coders/json.h" -#include "../files/files.h" -#include "../files/engine_paths.h" -#include "../data/dynamic.h" -#include "../logic/scripting/scripting.h" +#include "../coders/json.hpp" +#include "../files/files.hpp" +#include "../files/engine_paths.hpp" +#include "../data/dynamic.hpp" namespace fs = std::filesystem; @@ -75,7 +74,9 @@ ContentPack ContentPack::read(fs::path folder) { auto dependencies = root->list("dependencies"); if (dependencies) { for (size_t i = 0; i < dependencies->size(); i++) { - pack.dependencies.push_back(dependencies->str(i)); + pack.dependencies.push_back( + {DependencyLevel::required, dependencies->str(i)} + ); } } @@ -111,21 +112,6 @@ void ContentPack::scanFolder( } } -void ContentPack::scan( - fs::path rootfolder, - EnginePaths* paths, - std::vector& packs -) { - scanFolder(paths->getResources()/fs::path("content"), packs); - scanFolder(paths->getUserfiles()/fs::path("content"), packs); - scanFolder(rootfolder, packs); -} - -void ContentPack::scan(EnginePaths* paths, - std::vector& packs) { - scan(paths->getWorldFolder()/fs::path("content"), paths, packs); -} - std::vector ContentPack::worldPacksList(fs::path folder) { fs::path listfile = folder / fs::path("packs.list"); if (!fs::is_regular_file(listfile)) { @@ -152,23 +138,12 @@ fs::path ContentPack::findPack(const EnginePaths* paths, fs::path worldDir, std: return folder; } -void ContentPack::readPacks(const EnginePaths* paths, - std::vector& packs, - const std::vector& packnames, - fs::path worldDir) { - for (const auto& name : packnames) { - fs::path packfolder = ContentPack::findPack(paths, worldDir, name); - if (!fs::is_directory(packfolder)) { - throw contentpack_error(name, packfolder, - "could not to find pack '"+name+"'"); - } - packs.push_back(ContentPack::read(packfolder)); - } -} - ContentPackRuntime::ContentPackRuntime( ContentPack info, - std::unique_ptr env + scriptenv env ) : info(info), env(std::move(env)) { } + +ContentPackRuntime::~ContentPackRuntime() { +} diff --git a/src/content/ContentPack.h b/src/content/ContentPack.hpp similarity index 70% rename from src/content/ContentPack.h rename to src/content/ContentPack.hpp index c6ce7c86..d8790b8b 100644 --- a/src/content/ContentPack.h +++ b/src/content/ContentPack.hpp @@ -1,5 +1,7 @@ -#ifndef CONTENT_CONTENT_PACK_H_ -#define CONTENT_CONTENT_PACK_H_ +#ifndef CONTENT_CONTENT_PACK_HPP_ +#define CONTENT_CONTENT_PACK_HPP_ + +#include "../typedefs.hpp" #include #include @@ -26,6 +28,19 @@ public: fs::path getFolder() const; }; +enum class DependencyLevel { + required, // dependency must be installed + optional, // dependency will be installed if found + weak, // dependency will not be installed automatically +}; + + +/// @brief Content-pack that should be installed before the dependent +struct DependencyPack { + DependencyLevel level; + std::string id; +}; + struct ContentPack { std::string id = "none"; std::string title = "untitled"; @@ -33,7 +48,7 @@ struct ContentPack { std::string creator = ""; std::string description = "no description"; fs::path folder; - std::vector dependencies; + std::vector dependencies; fs::path getContentFile() const; @@ -50,16 +65,6 @@ struct ContentPack { fs::path folder, std::vector& packs ); - - static void scan( - fs::path folder, - EnginePaths* paths, - std::vector& packs - ); - static void scan( - EnginePaths* paths, - std::vector& packs - ); static std::vector worldPacksList(fs::path folder); @@ -68,13 +73,6 @@ struct ContentPack { fs::path worldDir, std::string name ); - - static void readPacks( - const EnginePaths* paths, - std::vector& packs, - const std::vector& names, - fs::path worldDir - ); }; struct ContentPackStats { @@ -89,12 +87,13 @@ struct ContentPackStats { class ContentPackRuntime { ContentPack info; ContentPackStats stats {}; - std::unique_ptr env; + scriptenv env; public: ContentPackRuntime( ContentPack info, - std::unique_ptr env + scriptenv env ); + ~ContentPackRuntime(); inline const ContentPackStats& getStats() const { return stats; @@ -112,9 +111,9 @@ public: return info; } - inline scripting::Environment* getEnvironment() const { - return env.get(); + inline scriptenv getEnvironment() const { + return env; } }; -#endif // CONTENT_CONTENT_PACK_H_ +#endif // CONTENT_CONTENT_PACK_HPP_ diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp new file mode 100644 index 00000000..c1d4ea14 --- /dev/null +++ b/src/content/PacksManager.cpp @@ -0,0 +1,158 @@ +#include "PacksManager.hpp" + +#include "../util/listutil.hpp" + +#include +#include + +PacksManager::PacksManager() { +} + +void PacksManager::setSources(std::vector sources) { + this->sources = sources; +} + +void PacksManager::scan() { + packs.clear(); + + std::vector packsList; + for (auto& folder : sources) { + ContentPack::scanFolder(folder, packsList); + for (auto& pack : packsList) { + packs.emplace(pack.id, pack); + } + } +} + +void PacksManager::exclude(const std::string& id) { + packs.erase(id); +} + +std::vector PacksManager::getAllNames() const { + std::vector names; + for (auto& entry : packs) { + names.push_back(entry.first); + } + return names; +} + +std::vector PacksManager::getAll(const std::vector& names) const { + std::vector packsList; + for (auto& name : names) { + auto found = packs.find(name); + if (found == packs.end()) { + throw contentpack_error(name, fs::path(""), "pack not found"); + } + packsList.push_back(found->second); + } + return packsList; +} + +static contentpack_error on_circular_dependency(std::queue& queue) { + const ContentPack* lastPack = queue.back(); + // circular dependency + std::stringstream ss; + ss << "circular dependency: " << lastPack->id; + while (!queue.empty()) { + auto* pack = queue.front(); + queue.pop(); + ss << " <- " << pack->id; + } + return contentpack_error(lastPack->id, lastPack->folder, ss.str()); +} + +/// @brief Resolve pack dependencies +/// @param pack current pack +/// @param packs all available packs repository +/// @param allNames all already done or enqueued packs +/// @param added packs with all dependencies resolved +/// @param queue current pass queue +/// @param resolveWeaks make weak dependencies resolved if found but not added to queue +/// @return true if all dependencies are already added or not found (optional/weak) +/// @throws contentpack_error if required dependency is not found +static bool resolve_dependencies ( + const ContentPack* pack, + const std::unordered_map& packs, + std::vector& allNames, + std::vector& added, + std::queue& queue, + bool resolveWeaks +) { + bool satisfied = true; + for (auto& dep : pack->dependencies) { + if (util::contains(added, dep.id)) { + continue; + } + auto found = packs.find(dep.id); + bool exists = found != packs.end(); + if (!exists && dep.level == DependencyLevel::required) { + throw contentpack_error(dep.id, fs::path(), "dependency of '"+pack->id+"'"); + } + if (!exists) { + // ignored for optional or weak dependencies + continue; + } + if (resolveWeaks && dep.level == DependencyLevel::weak) { + // dependency pack is found but not added yet + // resolveWeaks is used on second iteration, so it's will not be added + continue; + } + + if (!util::contains(allNames, dep.id) && dep.level != DependencyLevel::weak) { + allNames.push_back(dep.id); + queue.push(&found->second); + } + satisfied = false; + } + return satisfied; +} + +std::vector PacksManager::assembly(const std::vector& names) const { + std::vector allNames = names; + std::vector added; + std::queue queue; + std::queue queue2; + + for (auto& name : names) { + auto found = packs.find(name); + if (found == packs.end()) { + throw contentpack_error(name, fs::path(""), "pack not found"); + } + queue.push(&found->second); + } + + bool resolveWeaks = false; + while (!queue.empty()) { + int addedInIteration = 0; + while (!queue.empty()) { + auto* pack = queue.front(); + queue.pop(); + + if (resolve_dependencies(pack, packs, allNames, added, queue, resolveWeaks)) { + if (util::contains(added, pack->id)) { + throw contentpack_error(pack->id, pack->folder, "pack duplication"); + } + added.push_back(pack->id); + addedInIteration++; + } else { + queue2.push(pack); + } + } + std::swap(queue, queue2); + + // nothing done but deferring + if (addedInIteration == 0 && resolveWeaks) { + throw on_circular_dependency(queue); + } + resolveWeaks = true; + } + return added; +} + +std::vector PacksManager::getNames(const std::vector& packs) { + std::vector result; + for (const auto& pack : packs) { + result.push_back(pack.id); + } + return result; +} diff --git a/src/content/PacksManager.hpp b/src/content/PacksManager.hpp new file mode 100644 index 00000000..41c9c179 --- /dev/null +++ b/src/content/PacksManager.hpp @@ -0,0 +1,47 @@ +#ifndef CONTENT_PACKS_MANAGER_HPP_ +#define CONTENT_PACKS_MANAGER_HPP_ + +#include "ContentPack.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +class PacksManager { + std::unordered_map packs; + std::vector sources; +public: + PacksManager(); + + /// @brief Set content packs sources (search folders) + void setSources(std::vector sources); + + /// @brief Scan sources and collect all found packs excluding duplication. + /// Scanning order depends on sources order + void scan(); + + /// @brief Remove pack from manager to make it invisible for assembly(...) + void exclude(const std::string& id); + + /// @brief Get all found packs + std::vector getAllNames() const; + + /// @brief Get packs by names (id) + /// @param names pack names + /// @throws contentpack_error if pack not found + std::vector getAll(const std::vector& names) const; + + /// @brief Resolve all dependencies and fix packs order + /// @param names required packs (method can add extra packs) + /// @return resulting ordered vector of pack names + /// @throws contentpack_error if required dependency not found or + /// circular dependency detected + std::vector assembly(const std::vector& names) const; + + /// @brief Collect all pack names (identifiers) into a new vector + static std::vector getNames(const std::vector& packs); +}; + +#endif // CONTENT_PACKS_MANAGER_HPP_ diff --git a/src/core_defs.cpp b/src/core_defs.cpp index adf3c938..cb6eb9c4 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -1,43 +1,43 @@ -#include "core_defs.h" - -#include "items/ItemDef.h" -#include "content/Content.h" -#include "window/Window.h" -#include "window/Events.h" -#include "window/input.h" -#include "voxels/Block.h" - -// All in-game definitions (blocks, items, etc..) -void corecontent::setup(ContentBuilder* builder) { - Block& block = builder->createBlock("core:air"); - block.replaceable = true; - block.drawGroup = 1; - block.lightPassing = true; - block.skyLightPassing = true; - block.obstacle = false; - block.selectable = false; - block.model = BlockModel::none; - block.pickingItem = "core:empty"; - - ItemDef& item = builder->createItem("core:empty"); - item.iconType = item_icon_type::none; -} - -void corecontent::setup_bindings() { - Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W); - Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S); - Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D); - Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A); - Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE); - Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL); - Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT); - Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R); - Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C); - Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4); - Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N); - Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F); - Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1); - Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2); - Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3); - Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB); -} \ No newline at end of file +#include "core_defs.hpp" + +#include "items/ItemDef.hpp" +#include "content/Content.hpp" +#include "window/Window.hpp" +#include "window/Events.hpp" +#include "window/input.hpp" +#include "voxels/Block.hpp" + +// All in-game definitions (blocks, items, etc..) +void corecontent::setup(ContentBuilder* builder) { + Block& block = builder->createBlock("core:air"); + block.replaceable = true; + block.drawGroup = 1; + block.lightPassing = true; + block.skyLightPassing = true; + block.obstacle = false; + block.selectable = false; + block.model = BlockModel::none; + block.pickingItem = "core:empty"; + + ItemDef& item = builder->createItem("core:empty"); + item.iconType = item_icon_type::none; +} + +void corecontent::setup_bindings() { + Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W); + Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S); + Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D); + Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A); + Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE); + Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL); + Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT); + Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R); + Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C); + Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4); + Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N); + Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F); + Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1); + Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2); + Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3); + Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB); +} diff --git a/src/core_defs.h b/src/core_defs.hpp similarity index 86% rename from src/core_defs.h rename to src/core_defs.hpp index f9ff9d9a..f071dd1d 100644 --- a/src/core_defs.h +++ b/src/core_defs.hpp @@ -1,5 +1,5 @@ -#ifndef CORE_DEFS_H_ -#define CORE_DEFS_H_ +#ifndef CORE_DEFS_HPP_ +#define CORE_DEFS_HPP_ #include @@ -8,7 +8,7 @@ inline const std::string CORE_AIR = "core:air"; inline const std::string TEXTURE_NOTFOUND = "notfound"; -/* bindings used in engine code */ +// built-in bindings inline const std::string BIND_MOVE_FORWARD = "movement.forward"; inline const std::string BIND_MOVE_BACK = "movement.back"; inline const std::string BIND_MOVE_LEFT = "movement.left"; @@ -29,8 +29,8 @@ inline const std::string BIND_HUD_INVENTORY = "hud.inventory"; class ContentBuilder; namespace corecontent { - extern void setup_bindings(); - extern void setup(ContentBuilder* builder); + void setup_bindings(); + void setup(ContentBuilder* builder); } -#endif // CORE_DEFS_H_ +#endif // CORE_DEFS_HPP_ diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp index e7f1a10c..89ed67ff 100644 --- a/src/data/dynamic.cpp +++ b/src/data/dynamic.cpp @@ -1,4 +1,4 @@ -#include "dynamic.h" +#include "dynamic.hpp" #include @@ -10,34 +10,34 @@ List::~List() { std::string List::str(size_t index) const { const auto& val = values[index]; switch (val->type) { - case valtype::string: return *val->value.str; - case valtype::boolean: return val->value.boolean ? "true" : "false"; - case valtype::number: return std::to_string(val->value.decimal); - case valtype::integer: return std::to_string(val->value.integer); + case valtype::string: return std::get(val->value); + case valtype::boolean: return std::get(val->value) ? "true" : "false"; + case valtype::number: return std::to_string(std::get(val->value)); + case valtype::integer: return std::to_string(std::get(val->value)); default: throw std::runtime_error("type error"); } } -double List::num(size_t index) const { +number_t List::num(size_t index) const { const auto& val = values[index]; switch (val->type) { - case valtype::number: return val->value.decimal; - case valtype::integer: return val->value.integer; - case valtype::string: return std::stoll(*val->value.str); - case valtype::boolean: return val->value.boolean; + case valtype::number: return std::get(val->value); + case valtype::integer: return std::get(val->value); + case valtype::string: return std::stoll(std::get(val->value)); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } } -int64_t List::integer(size_t index) const { +integer_t List::integer(size_t index) const { const auto& val = values[index]; switch (val->type) { - case valtype::number: return val->value.decimal; - case valtype::integer: return val->value.integer; - case valtype::string: return std::stoll(*val->value.str); - case valtype::boolean: return val->value.boolean; + case valtype::number: return std::get(val->value); + case valtype::integer: return std::get(val->value); + case valtype::string: return std::stoll(std::get(val->value)); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } @@ -47,21 +47,21 @@ Map* List::map(size_t index) const { if (values[index]->type != valtype::map) { throw std::runtime_error("type error"); } - return values[index]->value.map; + return std::get(values[index]->value); } List* List::list(size_t index) const { if (values[index]->type != valtype::list) { throw std::runtime_error("type error"); } - return values[index]->value.list; + return std::get(values[index]->value); } bool List::flag(size_t index) const { const auto& val = values[index]; switch (val->type) { - case valtype::integer: return val->value.integer; - case valtype::boolean: return val->value.boolean; + case valtype::integer: return std::get(val->value); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } @@ -75,9 +75,7 @@ Value* List::getValueWriteable(size_t index) const { } List& List::put(std::string value) { - valvalue val; - val.str = new std::string(value); - values.push_back(std::make_unique(valtype::string, val)); + values.push_back(std::make_unique(valtype::string, value)); return *this; } @@ -90,9 +88,7 @@ List& List::put(int value) { } List& List::put(int64_t value) { - valvalue val; - val.integer = value; - values.push_back(std::make_unique(valtype::integer, val)); + values.push_back(std::make_unique(valtype::integer, value)); return *this; } @@ -101,9 +97,7 @@ List& List::put(uint64_t value) { } List& List::put(double value) { - valvalue val; - val.decimal = value; - values.push_back(std::make_unique(valtype::number, val)); + values.push_back(std::make_unique(valtype::number, value)); return *this; } @@ -112,23 +106,22 @@ List& List::put(float value) { } List& List::put(bool value) { - valvalue val; - val.boolean = value; - values.push_back(std::make_unique(valtype::boolean, val)); + values.push_back(std::make_unique(valtype::boolean, value)); + return *this; +} + +List& List::put(std::unique_ptr value) { + values.emplace_back(std::move(value)); return *this; } List& List::put(Map* value) { - valvalue val; - val.map = value; - values.push_back(std::make_unique(valtype::map, val)); + values.push_back(std::make_unique(valtype::map, value)); return *this; } List& List::put(List* value) { - valvalue val; - val.list = value; - values.push_back(std::make_unique(valtype::list, val)); + values.push_back(std::make_unique(valtype::list, value)); return *this; } @@ -189,38 +182,38 @@ std::string Map::getStr(std::string key, const std::string& def) const { return def; auto& val = found->second; switch (val->type) { - case valtype::string: return *val->value.str; - case valtype::boolean: return val->value.boolean ? "true" : "false"; - case valtype::number: return std::to_string(val->value.decimal); - case valtype::integer: return std::to_string(val->value.integer); + case valtype::string: return std::get(val->value); + case valtype::boolean: return std::get(val->value) ? "true" : "false"; + case valtype::number: return std::to_string(std::get(val->value)); + case valtype::integer: return std::to_string(std::get(val->value)); default: throw std::runtime_error("type error"); } } -double Map::getNum(std::string key, double def) const { +number_t Map::getNum(std::string key, double def) const { auto found = values.find(key); if (found == values.end()) return def; auto& val = found->second; switch (val->type) { - case valtype::number: return val->value.decimal; - case valtype::integer: return val->value.integer; - case valtype::string: return std::stoull(*val->value.str); - case valtype::boolean: return val->value.boolean; + case valtype::number: return std::get(val->value); + case valtype::integer: return std::get(val->value); + case valtype::string: return std::stoull(std::get(val->value)); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } } -int64_t Map::getInt(std::string key, int64_t def) const { +integer_t Map::getInt(std::string key, integer_t def) const { auto found = values.find(key); if (found == values.end()) return def; auto& val = found->second; switch (val->type) { - case valtype::number: return val->value.decimal; - case valtype::integer: return val->value.integer; - case valtype::string: return std::stoull(*val->value.str); - case valtype::boolean: return val->value.boolean; + case valtype::number: return std::get(val->value); + case valtype::integer: return std::get(val->value); + case valtype::string: return std::stoull(std::get(val->value)); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } } @@ -231,8 +224,8 @@ bool Map::getBool(std::string key, bool def) const { return def; auto& val = found->second; switch (val->type) { - case valtype::integer: return val->value.integer; - case valtype::boolean: return val->value.boolean; + case valtype::integer: return std::get(val->value); + case valtype::boolean: return std::get(val->value); default: throw std::runtime_error("type error"); } } @@ -271,7 +264,7 @@ Map* Map::map(std::string key) const { auto& val = found->second; if (val->type != valtype::map) return nullptr; - return val->value.map; + return std::get(val->value); } return nullptr; } @@ -279,7 +272,7 @@ Map* Map::map(std::string key) const { List* Map::list(std::string key) const { auto found = values.find(key); if (found != values.end()) - return found->second->value.list; + return std::get(found->second->value); return nullptr; } @@ -296,9 +289,7 @@ Map& Map::put(std::string key, int value) { } Map& Map::put(std::string key, int64_t value) { - valvalue val; - val.integer = value; - values[key] = std::make_unique(valtype::integer, val); + values[key] = std::make_unique(valtype::integer, value); return *this; } @@ -311,16 +302,12 @@ Map& Map::put(std::string key, float value) { } Map& Map::put(std::string key, double value) { - valvalue val; - val.decimal = value; - values[key] = std::make_unique(valtype::number, val); + values[key] = std::make_unique(valtype::number, value); return *this; } Map& Map::put(std::string key, std::string value){ - valvalue val; - val.str = new std::string(value); - values[key] = std::make_unique(valtype::string, val); + values[key] = std::make_unique(valtype::string, value); return *this; } @@ -329,26 +316,29 @@ Map& Map::put(std::string key, const char* value) { } Map& Map::put(std::string key, Map* value){ - valvalue val; - val.map = value; - values[key] = std::make_unique(valtype::map, val); + values[key] = std::make_unique(valtype::map, value); return *this; } Map& Map::put(std::string key, List* value){ - valvalue val; - val.list = value; - values[key] = std::make_unique(valtype::list, val); + values[key] = std::make_unique(valtype::list, value); return *this; } Map& Map::put(std::string key, bool value){ - valvalue val; - val.boolean = value; - values[key] = std::make_unique(valtype::boolean, val); + values[key] = std::make_unique(valtype::boolean, value); return *this; } +Map& Map::put(std::string key, std::unique_ptr value) { + values.emplace(key, value.release()); + return *this; +} + +void Map::remove(const std::string& key) { + values.erase(key); +} + List& Map::putList(std::string key) { List* arr = new List(); put(key, arr); @@ -365,15 +355,38 @@ bool Map::has(std::string key) { return values.find(key) != values.end(); } +size_t Map::size() const { + return values.size(); +} + Value::Value(valtype type, valvalue value) : type(type), value(value) { } Value::~Value() { switch (type) { - case valtype::map: delete value.map; break; - case valtype::list: delete value.list; break; - case valtype::string: delete value.str; break; + case valtype::map: delete std::get(value); break; + case valtype::list: delete std::get(value); break; default: break; } } + +std::unique_ptr Value::boolean(bool value) { + return std::make_unique(valtype::boolean, value); +} + +std::unique_ptr Value::of(number_u value) { + if (std::holds_alternative(value)) { + return std::make_unique(valtype::integer, std::get(value)); + } else { + return std::make_unique(valtype::number, std::get(value)); + } +} + +std::unique_ptr Value::of(const std::string& value) { + return std::make_unique(valtype::string, value); +} + +std::unique_ptr Value::of(std::unique_ptr value) { + return std::make_unique(valtype::map, value.release()); +} diff --git a/src/data/dynamic.h b/src/data/dynamic.hpp similarity index 72% rename from src/data/dynamic.h rename to src/data/dynamic.hpp index be8a2e39..7e9ef327 100644 --- a/src/data/dynamic.h +++ b/src/data/dynamic.hpp @@ -1,11 +1,13 @@ -#ifndef DATA_DYNAMIC_H_ -#define DATA_DYNAMIC_H_ +#ifndef DATA_DYNAMIC_HPP_ +#define DATA_DYNAMIC_HPP_ + +#include "../typedefs.hpp" #include #include #include +#include #include -#include "../typedefs.h" namespace dynamic { class Map; @@ -13,17 +15,17 @@ namespace dynamic { class Value; enum class valtype { - map, list, number, integer, string, boolean + none, map, list, number, integer, string, boolean }; - union valvalue { - Map* map; - List* list; - std::string* str; - double decimal; - int64_t integer; - uint64_t boolean; - }; + using valvalue = std::variant< + Map*, + List*, + std::string, + number_t, + bool, + integer_t + >; class Value { public: @@ -31,6 +33,11 @@ namespace dynamic { valvalue value; Value(valtype type, valvalue value); ~Value(); + + static std::unique_ptr boolean(bool value); + static std::unique_ptr of(number_u value); + static std::unique_ptr of(const std::string& value); + static std::unique_ptr of(std::unique_ptr value); }; class List { @@ -39,8 +46,8 @@ namespace dynamic { ~List(); std::string str(size_t index) const; - double num(size_t index) const; - int64_t integer(size_t index) const; + number_t num(size_t index) const; + integer_t integer(size_t index) const; Map* map(size_t index) const; List* list(size_t index) const; bool flag(size_t index) const; @@ -63,6 +70,7 @@ namespace dynamic { List& put(Map* value); List& put(List* value); List& put(bool value); + List& put(std::unique_ptr value); Value* getValueWriteable(size_t index) const; @@ -78,13 +86,13 @@ namespace dynamic { ~Map(); std::string getStr(std::string key) const; - double getNum(std::string key) const; - int64_t getInt(std::string key) const; + number_t getNum(std::string key) const; + integer_t getInt(std::string key) const; bool getBool(std::string key) const; std::string getStr(std::string key, const std::string& def) const; - double getNum(std::string key, double def) const; - int64_t getInt(std::string key, int64_t def) const; + number_t getNum(std::string key, double def) const; + integer_t getInt(std::string key, integer_t def) const; bool getBool(std::string key, bool def) const; void str(std::string key, std::string& dst) const; @@ -110,12 +118,16 @@ namespace dynamic { Map& put(std::string key, Map* value); Map& put(std::string key, List* value); Map& put(std::string key, bool value); + Map& put(std::string key, std::unique_ptr value); + + void remove(const std::string& key); List& putList(std::string key); Map& putMap(std::string key); bool has(std::string key); + size_t size() const; }; } -#endif // DATA_DYNAMIC_H_ +#endif // DATA_DYNAMIC_HPP_ diff --git a/src/data/setting.cpp b/src/data/setting.cpp new file mode 100644 index 00000000..56f166ce --- /dev/null +++ b/src/data/setting.cpp @@ -0,0 +1,38 @@ +#include "setting.hpp" + +#include "../util/stringutil.hpp" + +std::string NumberSetting::toString() const { + switch (getFormat()) { + case setting_format::simple: + return util::to_string(value); + case setting_format::percent: + return std::to_string(static_cast(round(value * 100))) + "%"; + default: + return "invalid format"; + } +} + +std::string IntegerSetting::toString() const { + switch (getFormat()) { + case setting_format::simple: + return util::to_string(value); + case setting_format::percent: + return std::to_string(value) + "%"; + default: + return "invalid format"; + } +} + +std::string FlagSetting::toString() const { + switch (getFormat()) { + case setting_format::simple: + return value ? "true" : "false"; + default: + return "invalid format"; + } +} + +std::string StringSetting::toString() const { + return value; +} diff --git a/src/data/setting.hpp b/src/data/setting.hpp new file mode 100644 index 00000000..e0efffbd --- /dev/null +++ b/src/data/setting.hpp @@ -0,0 +1,180 @@ +#ifndef DATA_SETTING_HPP_ +#define DATA_SETTING_HPP_ + +#include +#include +#include +#include + +#include "../typedefs.hpp" +#include "../delegates.hpp" + +enum class setting_format { + simple, percent +}; + +class Setting { +protected: + setting_format format; +public: + Setting(setting_format format) : format(format) { + } + + virtual ~Setting() {} + + virtual void resetToDefault() = 0; + + virtual setting_format getFormat() const { + return format; + } + + virtual std::string toString() const = 0; +}; + +template +class ObservableSetting : public Setting { + int nextid = 1; + std::unordered_map> observers; +protected: + T initial; + T value; +public: + ObservableSetting(T value, setting_format format) + : Setting(format), initial(value), value(value) {} + + observer_handler observe(consumer callback, bool callOnStart=false) { + const int id = nextid++; + observers.emplace(id, callback); + if (callOnStart) { + callback(value); + } + return std::shared_ptr(new int(id), [this](int* id) { + observers.erase(*id); + delete id; + }); + } + + const T& get() const { + return value; + } + + T& operator*() { + return value; + } + + void notify(T value) { + for (auto& entry : observers) { + entry.second(value); + } + } + + void set(T value) { + if (value == this->value) { + return; + } + this->value = value; + notify(value); + } + + virtual void resetToDefault() override { + set(initial); + } +}; + +class NumberSetting : public ObservableSetting { +protected: + number_t min; + number_t max; +public: + NumberSetting( + number_t value, + number_t min=std::numeric_limits::min(), + number_t max=std::numeric_limits::max(), + setting_format format=setting_format::simple + ) : ObservableSetting(value, format), + min(min), + max(max) + {} + + number_t& operator*() { + return value; + } + + number_t get() const { + return value; + } + + number_t getMin() const { + return min; + } + + number_t getMax() const { + return max; + } + + number_t getT() const { + return (value - min) / (max - min); + } + + virtual std::string toString() const override; + + static inline NumberSetting createPercent(number_t def) { + return NumberSetting(def, 0.0, 1.0, setting_format::percent); + } +}; + +class IntegerSetting : public ObservableSetting { +protected: + integer_t min; + integer_t max; +public: + IntegerSetting( + integer_t value, + integer_t min=std::numeric_limits::min(), + integer_t max=std::numeric_limits::max(), + setting_format format=setting_format::simple + ) : ObservableSetting(value, format), + min(min), + max(max) + {} + + integer_t getMin() const { + return min; + } + + integer_t getMax() const { + return max; + } + + integer_t getT() const { + return (value - min) / (max - min); + } + + virtual std::string toString() const override; +}; + +class FlagSetting : public ObservableSetting { +public: + FlagSetting( + bool value, + setting_format format=setting_format::simple + ) : ObservableSetting(value, format) {} + + void toggle() { + set(!get()); + } + + virtual std::string toString() const override; +}; + +class StringSetting : public ObservableSetting { +public: + StringSetting( + std::string value, + setting_format format=setting_format::simple + ) : ObservableSetting(value, format) {} + + virtual std::string toString() const override; +}; + +#endif // DATA_SETTING_HPP_ diff --git a/src/debug/Logger.cpp b/src/debug/Logger.cpp new file mode 100644 index 00000000..c0f8ef38 --- /dev/null +++ b/src/debug/Logger.cpp @@ -0,0 +1,74 @@ +#include "Logger.hpp" + +#include +#include +#include + +using namespace debug; + +std::ofstream Logger::file; +std::mutex Logger::mutex; +std::string Logger::utcOffset = ""; +unsigned Logger::moduleLen = 20; + +LogMessage::~LogMessage() { + logger->log(level, ss.str()); +} + +Logger::Logger(std::string name) : name(name) { +} + +void Logger::log(LogLevel level, const std::string& name, std::string message) { + using namespace std::chrono; + + std::stringstream ss; + switch (level) { + case LogLevel::debug: +# ifdef NDEBUG + return; +# endif + ss << "[D]"; + break; + case LogLevel::info: + ss << "[I]"; + break; + case LogLevel::warning: + ss << "[W]"; + break; + case LogLevel::error: + ss << "[E]"; + break; + } + time_t tm = std::time(nullptr); + auto ms = duration_cast(system_clock::now().time_since_epoch()) % 1000; + ss << " " << std::put_time(std::localtime(&tm), "%Y/%m/%d %T"); + ss << '.' << std::setfill('0') << std::setw(3) << ms.count(); + ss << utcOffset << " (" << std::setfill(' ') << std::setw(moduleLen) << name << ") "; + ss << message; + { + std::lock_guard lock(mutex); + auto string = ss.str(); + if (file.good()) { + file << string << '\n'; + } + std::cout << string << std::endl; + } +} + +void Logger::init(const std::string& filename) { + file.open(filename); + + time_t tm = std::time(nullptr); + std::stringstream ss; + ss << std::put_time(std::localtime(&tm), "%z"); + utcOffset = ss.str(); +} + +void Logger::flush() { + std::lock_guard lock(mutex); + file.flush(); +} + +void Logger::log(LogLevel level, std::string message) { + log(level, name, message); +} diff --git a/src/debug/Logger.hpp b/src/debug/Logger.hpp new file mode 100644 index 00000000..6bb2ff5c --- /dev/null +++ b/src/debug/Logger.hpp @@ -0,0 +1,66 @@ +#ifndef DEBUG_LOGGER_HPP_ +#define DEBUG_LOGGER_HPP_ + +#include +#include +#include +#include + +namespace debug { + enum class LogLevel { + debug, info, warning, error + }; + + class Logger; + + class LogMessage { + Logger* logger; + LogLevel level; + std::stringstream ss; + public: + LogMessage(Logger* logger, LogLevel level) : logger(logger), level(level) {} + ~LogMessage(); + + template + LogMessage& operator<<(const T& x) { + ss << x; + return *this; + } + }; + + class Logger { + static std::mutex mutex; + static std::string utcOffset; + static std::ofstream file; + static unsigned moduleLen; + + std::string name; + + static void log(LogLevel level, const std::string& name, std::string message); + public: + static void init(const std::string& filename); + static void flush(); + + Logger(std::string name); + + void log(LogLevel level, std::string message); + + LogMessage debug() { + return LogMessage(this, LogLevel::debug); + } + + LogMessage info() { + return LogMessage(this, LogLevel::info); + } + + LogMessage error() { + return LogMessage(this, LogLevel::error); + } + + LogMessage warning() { + return LogMessage(this, LogLevel::warning); + } + }; +} + +#endif // DEBUG_LOGGER_HPP_ diff --git a/src/delegates.h b/src/delegates.hpp similarity index 78% rename from src/delegates.h rename to src/delegates.hpp index debb7f59..fe1c95e6 100644 --- a/src/delegates.h +++ b/src/delegates.hpp @@ -1,11 +1,13 @@ -#ifndef DELEGATES_H_ -#define DELEGATES_H_ +#ifndef DELEGATES_HPP_ +#define DELEGATES_HPP_ #include #include #include using runnable = std::function; +template using supplier = std::function; +template using consumer = std::function; // data sources using wstringsupplier = std::function; @@ -20,4 +22,4 @@ using boolconsumer = std::function; using int_array_consumer = std::function; using wstringchecker = std::function; -#endif // DELEGATES_H_ +#endif // DELEGATES_HPP_ diff --git a/src/engine.cpp b/src/engine.cpp index 0ef8d9de..2f38e9b0 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,47 +1,46 @@ -#include "engine.h" +#include "engine.hpp" -#include -#include -#include -#include -#include -#include -#include -#include #define GLEW_STATIC -#include "audio/audio.h" -#include "assets/Assets.h" -#include "assets/AssetsLoader.h" -#include "world/WorldGenerators.h" -#include "voxels/DefaultWorldGenerator.h" -#include "voxels/FlatWorldGenerator.h" -#include "window/Window.h" -#include "window/Events.h" -#include "window/Camera.h" -#include "window/input.h" -#include "graphics/Batch2D.h" -#include "graphics/GfxContext.h" -#include "graphics/Shader.h" -#include "graphics/ImageData.h" -#include "frontend/gui/GUI.h" -#include "frontend/screens.h" -#include "frontend/menu/menu.h" -#include "util/platform.h" +#include "debug/Logger.hpp" +#include "assets/AssetsLoader.hpp" +#include "audio/audio.hpp" +#include "coders/GLSLExtension.hpp" +#include "coders/imageio.hpp" +#include "coders/json.hpp" +#include "coders/toml.hpp" +#include "content/ContentLoader.hpp" +#include "core_defs.hpp" +#include "files/files.hpp" +#include "files/settings_io.hpp" +#include "frontend/locale.hpp" +#include "frontend/menu.hpp" +#include "frontend/screens/Screen.hpp" +#include "frontend/screens/MenuScreen.hpp" +#include "graphics/core/Batch2D.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/ui/GUI.hpp" +#include "logic/EngineController.hpp" +#include "logic/scripting/scripting.hpp" +#include "util/listutil.hpp" +#include "util/platform.hpp" +#include "voxels/DefaultWorldGenerator.hpp" +#include "voxels/FlatWorldGenerator.hpp" +#include "window/Camera.hpp" +#include "window/Events.hpp" +#include "window/input.hpp" +#include "window/Window.hpp" +#include "world/WorldGenerators.hpp" -#include "coders/json.h" -#include "coders/png.h" -#include "coders/GLSLExtension.h" -#include "files/files.h" -#include "files/engine_paths.h" +#include +#include +#include +#include +#include -#include "content/Content.h" -#include "content/ContentPack.h" -#include "content/ContentLoader.h" -#include "frontend/locale/langs.h" -#include "logic/scripting/scripting.h" - -#include "core_defs.h" +static debug::Logger logger("engine"); namespace fs = std::filesystem; @@ -50,48 +49,67 @@ void addWorldGenerators() { WorldGenerators::addGenerator("core:flat"); } -Engine::Engine(EngineSettings& settings, EnginePaths* paths) - : settings(settings), paths(paths) -{ - if (Window::initialize(settings.display)){ +inline void create_channel(Engine* engine, std::string name, NumberSetting& setting) { + if (name != "master") { + audio::create_channel(name); + } + engine->keepAlive(setting.observe([=](auto value) { + audio::get_channel(name)->setVolume(value*value); + })); +} + +Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths) + : settings(settings), settingsHandler(settingsHandler), paths(paths) +{ + corecontent::setup_bindings(); + loadSettings(); + + controller = std::make_unique(this); + if (Window::initialize(&this->settings.display)){ throw initialize_error("could not initialize window"); } - audio::initialize(settings.audio.enabled); - audio::create_channel("regular"); - audio::create_channel("music"); - audio::create_channel("ambient"); - audio::create_channel("ui"); + audio::initialize(settings.audio.enabled.get()); + create_channel(this, "master", settings.audio.volumeMaster); + create_channel(this, "regular", settings.audio.volumeRegular); + create_channel(this, "music", settings.audio.volumeMusic); + create_channel(this, "ambient", settings.audio.volumeAmbient); + create_channel(this, "ui", settings.audio.volumeUI); - auto resdir = paths->getResources(); - scripting::initialize(this); - - std::cout << "-- loading assets" << std::endl; - std::vector roots {resdir}; - - resPaths = std::make_unique(resdir, roots); - assets = std::make_unique(); - - AssetsLoader loader(assets.get(), resPaths.get()); - AssetsLoader::addDefaults(loader, nullptr); - - Shader::preprocessor->setPaths(resPaths.get()); - while (loader.hasNext()) { - if (!loader.loadNext()) { - assets.reset(); - scripting::close(); - Window::terminate(); - throw initialize_error("could not to load assets"); - } - } gui = std::make_unique(); - if (settings.ui.language == "auto") { - settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources()); + if (settings.ui.language.get() == "auto") { + settings.ui.language.set(langs::locale_by_envlocale( + platform::detect_locale(), + paths->getResources() + )); } - if (ENGINE_VERSION_INDEV) { + if (ENGINE_DEBUG_BUILD) { menus::create_version_label(this); } - setLanguage(settings.ui.language); + keepAlive(settings.ui.language.observe([=](auto lang) { + setLanguage(lang); + }, true)); addWorldGenerators(); + + scripting::initialize(this); +} + +void Engine::loadSettings() { + fs::path settings_file = paths->getSettingsFile(); + if (fs::is_regular_file(settings_file)) { + logger.info() << "loading settings"; + std::string text = files::read_string(settings_file); + toml::parse(settingsHandler, settings_file.string(), text); + } + fs::path controls_file = paths->getControlsFile(); + if (fs::is_regular_file(controls_file)) { + logger.info() << "loading controls"; + std::string text = files::read_string(controls_file); + Events::loadBindings(controls_file.u8string(), text); + } +} + +void Engine::onAssetsLoaded() { + gui->onAssetsLoad(assets.get()); } void Engine::updateTimers() { @@ -103,86 +121,126 @@ void Engine::updateTimers() { void Engine::updateHotkeys() { if (Events::jpressed(keycode::F2)) { - std::unique_ptr image(Window::takeScreenshot()); - image->flipY(); - fs::path filename = paths->getScreenshotFile("png"); - png::write_image(filename.string(), image.get()); - std::cout << "saved screenshot as " << filename << std::endl; + saveScreenshot(); } if (Events::jpressed(keycode::F11)) { - Window::toggleFullscreen(); + settings.display.fullscreen.toggle(); } } -inline constexpr float sqr(float x) { - return x*x; -} - -static void updateAudio(double delta, const AudioSettings& settings) { - audio::get_channel("master")->setVolume(sqr(settings.volumeMaster)); - audio::get_channel("regular")->setVolume(sqr(settings.volumeRegular)); - audio::get_channel("ui")->setVolume(sqr(settings.volumeUI)); - audio::get_channel("ambient")->setVolume(sqr(settings.volumeAmbient)); - audio::get_channel("music")->setVolume(sqr(settings.volumeMusic)); - audio::update(delta); +void Engine::saveScreenshot() { + auto image = Window::takeScreenshot(); + image->flipY(); + fs::path filename = paths->getScreenshotFile("png"); + imageio::write(filename.string(), image.get()); + logger.info() << "saved screenshot as " << filename.u8string(); } void Engine::mainloop() { + logger.info() << "starting menu screen"; setScreen(std::make_shared(this)); Batch2D batch(1024); lastTime = Window::time(); - - std::cout << "-- initialized" << std::endl; + + logger.info() << "engine started"; while (!Window::isShouldClose()){ assert(screen != nullptr); updateTimers(); updateHotkeys(); - updateAudio(delta, settings.audio); + audio::update(delta); - gui->act(delta); + gui->act(delta, Viewport(Window::width, Window::height)); screen->update(delta); if (!Window::isIconified()) { - screen->draw(delta); - - Viewport viewport(Window::width, Window::height); - GfxContext ctx(nullptr, viewport, &batch); - gui->draw(&ctx, assets.get()); - - Window::swapInterval(settings.display.swapInterval); - } else { - Window::swapInterval(1); + renderFrame(batch); } + Window::swapInterval(Window::isIconified() ? 1 : settings.display.vsync.get()); + + processPostRunnables(); + Window::swapBuffers(); Events::pollEvents(); } } -Engine::~Engine() { - std::cout << "-- shutting down" << std::endl; - if (screen) { - screen->onEngineShutdown(); - } - screen.reset(); - content.reset(); - assets.reset(); - audio::close(); - scripting::close(); - Window::terminate(); - std::cout << "-- engine finished" << std::endl; +void Engine::renderFrame(Batch2D& batch) { + screen->draw(delta); + + Viewport viewport(Window::width, Window::height); + DrawContext ctx(nullptr, viewport, &batch); + gui->draw(&ctx, assets.get()); } -inline const std::string checkPacks( - const std::unordered_set& packs, - const std::vector& dependencies -) { - for (const std::string& str : dependencies) { - if (packs.find(str) == packs.end()) { - return str; +void Engine::processPostRunnables() { + std::lock_guard lock(postRunnablesMutex); + while (!postRunnables.empty()) { + postRunnables.front()(); + postRunnables.pop(); + } + scripting::process_post_runnables(); +} + +void Engine::saveSettings() { + logger.info() << "saving settings"; + files::write_string(paths->getSettingsFile(), toml::stringify(settingsHandler)); + files::write_string(paths->getControlsFile(), Events::writeBindings()); +} + +Engine::~Engine() { + saveSettings(); + logger.info() << "shutting down"; + if (screen) { + screen->onEngineShutdown(); + screen.reset(); + } + content.reset(); + assets.reset(); + gui.reset(); + logger.info() << "gui finished"; + audio::close(); + scripting::close(); + logger.info() << "scripting finished"; + Window::terminate(); + logger.info() << "engine finished"; +} + +EngineController* Engine::getController() { + return controller.get(); +} + +PacksManager Engine::createPacksManager(const fs::path& worldFolder) { + PacksManager manager; + manager.setSources({ + worldFolder/fs::path("content"), + paths->getUserfiles()/fs::path("content"), + paths->getResources()/fs::path("content") + }); + return manager; +} + +void Engine::loadAssets() { + logger.info() << "loading assets"; + Shader::preprocessor->setPaths(resPaths.get()); + + auto new_assets = std::make_unique(); + AssetsLoader loader(new_assets.get(), resPaths.get()); + AssetsLoader::addDefaults(loader, content.get()); + + bool threading = false; + if (threading) { + auto task = loader.startTask([=](){}); + task->waitForEnd(); + } else { + while (loader.hasNext()) { + if (!loader.loadNext()) { + new_assets.reset(); + throw std::runtime_error("could not to load assets"); + } } } - return ""; + assets.reset(new_assets.release()); } void Engine::loadContent() { @@ -191,65 +249,57 @@ void Engine::loadContent() { corecontent::setup(&contentBuilder); paths->setContentPacks(&contentPacks); - std::vector resRoots; - std::vector srcPacks = contentPacks; - contentPacks.clear(); - - std::string missingDependency; - std::unordered_set loadedPacks, existingPacks; - for (const auto& item : srcPacks) { - existingPacks.insert(item.id); + std::vector names; + for (auto& pack : contentPacks) { + names.push_back(pack.id); } + PacksManager manager = createPacksManager(paths->getWorldFolder()); + manager.scan(); + names = manager.assembly(names); + contentPacks = manager.getAll(names); - while (existingPacks.size() > loadedPacks.size()) { - for (auto& pack : srcPacks) { - if(loadedPacks.find(pack.id) != loadedPacks.end()) { - continue; - } - missingDependency = checkPacks(existingPacks, pack.dependencies); - if (!missingDependency.empty()) { - throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'"); - } - if (pack.dependencies.empty() || checkPacks(loadedPacks, pack.dependencies).empty()) { - loadedPacks.insert(pack.id); - resRoots.push_back(pack.folder); - contentPacks.push_back(pack); - ContentLoader loader(&pack); - loader.load(contentBuilder); - } - } + std::vector> resRoots; + for (auto& pack : contentPacks) { + resRoots.push_back({pack.id, pack.folder}); + + ContentLoader loader(&pack); + loader.load(contentBuilder); } - content.reset(contentBuilder.build()); - resPaths.reset(new ResPaths(resdir, resRoots)); + resPaths = std::make_unique(resdir, resRoots); - Shader::preprocessor->setPaths(resPaths.get()); + langs::setup(resdir, langs::current->getId(), contentPacks); + loadAssets(); + onAssetsLoaded(); +} - std::unique_ptr new_assets(new Assets()); - std::cout << "-- loading assets" << std::endl; - AssetsLoader loader(new_assets.get(), resPaths.get()); - AssetsLoader::addDefaults(loader, content.get()); - while (loader.hasNext()) { - if (!loader.loadNext()) { - new_assets.reset(); - throw std::runtime_error("could not to load assets"); - } - } - assets->extend(*new_assets.get()); +void Engine::resetContent() { + auto manager = createPacksManager(fs::path()); + manager.scan(); + contentPacks = manager.getAll(basePacks); + loadContent(); } void Engine::loadWorldContent(const fs::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); - ContentPack::readPacks(paths, contentPacks, packNames, folder); + PacksManager manager; + manager.setSources({ + folder/fs::path("content"), + paths->getUserfiles()/fs::path("content"), + paths->getResources()/fs::path("content") + }); + manager.scan(); + contentPacks = manager.getAll(manager.assembly(packNames)); paths->setWorldFolder(folder); loadContent(); } void Engine::loadAllPacks() { - auto resdir = paths->getResources(); - contentPacks.clear(); - ContentPack::scan(paths, contentPacks); + PacksManager manager = createPacksManager(paths->getWorldFolder()); + manager.scan(); + auto allnames = manager.getAllNames(); + contentPacks = manager.getAll(manager.assembly(allnames)); } double Engine::getDelta() const { @@ -263,9 +313,8 @@ void Engine::setScreen(std::shared_ptr screen) { } void Engine::setLanguage(std::string locale) { - settings.ui.language = locale; langs::setup(paths->getResources(), locale, contentPacks); - menus::create_menus(this); + gui->getMenu()->setPageLoader(menus::create_page_loader(this)); } gui::GUI* Engine::getGUI() { @@ -288,6 +337,10 @@ std::vector& Engine::getContentPacks() { return contentPacks; } +std::vector& Engine::getBasePacks() { + return basePacks; +} + EnginePaths* Engine::getPaths() { return paths; } @@ -299,3 +352,12 @@ ResPaths* Engine::getResPaths() { std::shared_ptr Engine::getScreen() { return screen; } + +void Engine::postRunnable(runnable callback) { + std::lock_guard lock(postRunnablesMutex); + postRunnables.push(callback); +} + +SettingsHandler& Engine::getSettingsHandler() { + return settingsHandler; +} diff --git a/src/engine.h b/src/engine.hpp similarity index 61% rename from src/engine.h rename to src/engine.hpp index 1c2d0801..331864ed 100644 --- a/src/engine.h +++ b/src/engine.hpp @@ -1,23 +1,32 @@ -#ifndef SRC_ENGINE_H_ -#define SRC_ENGINE_H_ +#ifndef ENGINE_HPP_ +#define ENGINE_HPP_ + +#include "delegates.hpp" +#include "settings.hpp" +#include "typedefs.hpp" + +#include "assets/Assets.hpp" +#include "content/Content.hpp" +#include "content/ContentPack.hpp" +#include "content/PacksManager.hpp" +#include "files/engine_paths.hpp" +#include "util/ObjectsKeeper.hpp" -#include -#include -#include -#include #include -#include "typedefs.h" -#include "settings.h" - -#include "assets/Assets.h" -#include "content/Content.h" -#include "content/ContentPack.h" -#include "files/engine_paths.h" +#include +#include +#include +#include +#include +#include class Level; class Screen; class EnginePaths; class ResPaths; +class Batch2D; +class EngineController; +class SettingsHandler; namespace fs = std::filesystem; @@ -30,14 +39,20 @@ public: initialize_error(const std::string& message) : std::runtime_error(message) {} }; -class Engine { +class Engine : public util::ObjectsKeeper { + EngineSettings& settings; + SettingsHandler& settingsHandler; + EnginePaths* paths; + std::unique_ptr assets = nullptr; std::shared_ptr screen = nullptr; std::vector contentPacks; - EngineSettings& settings; std::unique_ptr content = nullptr; - EnginePaths* paths; std::unique_ptr resPaths = nullptr; + std::queue postRunnables; + std::recursive_mutex postRunnablesMutex; + std::unique_ptr controller; + std::vector basePacks {"base"}; uint64_t frame = 0; double lastTime = 0.0; @@ -45,16 +60,23 @@ class Engine { std::unique_ptr gui; + void loadSettings(); + void saveSettings(); void updateTimers(); void updateHotkeys(); + void renderFrame(Batch2D& batch); + void processPostRunnables(); + void loadAssets(); public: - Engine(EngineSettings& settings, EnginePaths* paths); + Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths); ~Engine(); /// @brief Start main engine input/update/render loop. /// Automatically sets MenuScreen void mainloop(); + /// @brief Called after assets loading when all engine systems are initialized + void onAssetsLoaded(); /// @brief Set screen (scene). /// nullptr may be used to delete previous screen before creating new one, @@ -68,6 +90,8 @@ public: /// @brief Load all selected content-packs and reload assets void loadContent(); + + void resetContent(); /// @brief Collect world content-packs and load content /// @see loadContent @@ -101,8 +125,21 @@ public: /// @brief Get selected content packs std::vector& getContentPacks(); + std::vector& getBasePacks(); + /// @brief Get current screen std::shared_ptr getScreen(); + + /// @brief Enqueue function call to the end of current frame in draw thread + void postRunnable(runnable callback); + + void saveScreenshot(); + + EngineController* getController(); + + PacksManager createPacksManager(const fs::path& worldFolder); + + SettingsHandler& getSettingsHandler(); }; -#endif // SRC_ENGINE_H_ +#endif // ENGINE_HPP_ diff --git a/src/files/WorldConverter.cpp b/src/files/WorldConverter.cpp index ba30f7bf..37bc5cee 100644 --- a/src/files/WorldConverter.cpp +++ b/src/files/WorldConverter.cpp @@ -1,30 +1,46 @@ -#include "WorldConverter.h" +#include "WorldConverter.hpp" + +#include "WorldFiles.hpp" + +#include "../content/ContentLUT.hpp" +#include "../data/dynamic.hpp" +#include "../debug/Logger.hpp" +#include "../files/files.hpp" +#include "../objects/Player.hpp" +#include "../util/ThreadPool.hpp" +#include "../voxels/Chunk.hpp" #include #include #include -#include "WorldFiles.h" - -#include "../data/dynamic.h" -#include "../files/files.h" -#include "../voxels/Chunk.h" -#include "../content/ContentLUT.h" -#include "../objects/Player.h" namespace fs = std::filesystem; +static debug::Logger logger("world-converter"); + +class ConverterWorker : public util::Worker { + std::shared_ptr converter; +public: + ConverterWorker(std::shared_ptr converter) + : converter(converter) {} + + int operator()(const std::shared_ptr& task) override { + converter->convert(*task); + return 0; + } +}; + WorldConverter::WorldConverter( fs::path folder, const Content* content, std::shared_ptr lut -) : lut(lut), content(content) +) : wfile(std::make_unique(folder)), + lut(lut), + content(content) { - DebugSettings settings; - wfile = new WorldFiles(folder, settings); - - fs::path regionsFolder = wfile->getRegionsFolder(); + fs::path regionsFolder = wfile->getRegions().getRegionsFolder(REGION_LAYER_VOXELS); if (!fs::is_directory(regionsFolder)) { - std::cerr << "nothing to convert" << std::endl; + logger.error() << "nothing to convert"; return; } tasks.push(convert_task {convert_task_type::player, wfile->getPlayerFile()}); @@ -34,52 +50,68 @@ WorldConverter::WorldConverter( } WorldConverter::~WorldConverter() { - delete wfile; } -bool WorldConverter::hasNext() const { - return !tasks.empty(); +std::shared_ptr WorldConverter::startTask( + fs::path folder, + const Content* content, + std::shared_ptr lut, + runnable onDone, + bool multithreading +) { + auto converter = std::make_shared(folder, content, lut); + if (!multithreading) { + converter->setOnComplete([=]() { + converter->write(); + onDone(); + }); + return converter; + } + auto pool = std::make_shared>( + "converter-pool", + [=](){return std::make_shared(converter);}, + [=](int& _) {} + ); + while (!converter->tasks.empty()) { + const convert_task& task = converter->tasks.front(); + auto ptr = std::make_shared(task); + pool->enqueueJob(ptr); + converter->tasks.pop(); + } + pool->setOnComplete([=]() { + converter->write(); + onDone(); + }); + return pool; } -void WorldConverter::convertRegion(fs::path file) { +void WorldConverter::convertRegion(fs::path file) const { int x, z; std::string name = file.stem().string(); - if (!WorldFiles::parseRegionFilename(name, x, z)) { - std::cerr << "could not parse name " << name << std::endl; + if (!WorldRegions::parseRegionFilename(name, x, z)) { + logger.error() << "could not parse name " << name; return; } - std::cout << "converting region " << name << std::endl; - for (uint cz = 0; cz < REGION_SIZE; cz++) { - for (uint cx = 0; cx < REGION_SIZE; cx++) { - int gx = cx + x * REGION_SIZE; - int gz = cz + z * REGION_SIZE; - std::unique_ptr data (wfile->getChunk(gx, gz)); - if (data == nullptr) - continue; - if (lut) { - Chunk::convert(data.get(), lut.get()); - } - wfile->put(gx, gz, data.get()); + logger.info() << "converting region " << name; + wfile->getRegions().processRegionVoxels(x, z, [=](ubyte* data) { + if (lut) { + Chunk::convert(data, lut.get()); } - } + return true; + }); } -void WorldConverter::convertPlayer(fs::path file) { - std::cout << "converting player " << file.u8string() << std::endl; +void WorldConverter::convertPlayer(fs::path file) const { + logger.info() << "converting player " << file.u8string(); auto map = files::read_json(file); Player::convert(map.get(), lut.get()); files::write_json(file, map.get()); } -void WorldConverter::convertNext() { - if (!hasNext()) { - throw std::runtime_error("no more regions to convert"); - } - convert_task task = tasks.front(); - tasks.pop(); - +void WorldConverter::convert(convert_task task) const { if (!fs::is_regular_file(task.file)) return; + switch (task.type) { case convert_task_type::region: convertRegion(task.file); @@ -90,11 +122,51 @@ void WorldConverter::convertNext() { } } +void WorldConverter::convertNext() { + if (tasks.empty()) { + throw std::runtime_error("no more regions to convert"); + } + convert_task task = tasks.front(); + tasks.pop(); + tasksDone++; + + convert(task); +} + +void WorldConverter::setOnComplete(runnable callback) { + this->onComplete = callback; +} + +void WorldConverter::update() { + convertNext(); + if (onComplete && tasks.empty()) { + onComplete(); + } +} + +void WorldConverter::terminate() { + tasks = {}; +} + +bool WorldConverter::isActive() const { + return !tasks.empty(); +} + void WorldConverter::write() { - std::cout << "writing world" << std::endl; + logger.info() << "writing world"; wfile->write(nullptr, content); } -uint WorldConverter::getTotalTasks() const { - return tasks.size(); +void WorldConverter::waitForEnd() { + while (isActive()) { + update(); + } +} + +uint WorldConverter::getWorkTotal() const { + return tasks.size() + tasksDone; +} + +uint WorldConverter::getWorkDone() const { + return tasksDone; } diff --git a/src/files/WorldConverter.h b/src/files/WorldConverter.h deleted file mode 100644 index 031b69cd..00000000 --- a/src/files/WorldConverter.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef FILES_WORLD_CONVERTER_H_ -#define FILES_WORLD_CONVERTER_H_ - -#include -#include -#include -#include "../typedefs.h" - -namespace fs = std::filesystem; - -class Content; -class ContentLUT; -class WorldFiles; - -enum class convert_task_type { - region, player -}; - -struct convert_task { - convert_task_type type; - fs::path file; -}; - -class WorldConverter { - WorldFiles* wfile; - std::shared_ptr const lut; - const Content* const content; - std::queue tasks; - - void convertPlayer(fs::path file); - void convertRegion(fs::path file); -public: - WorldConverter(fs::path folder, const Content* content, - std::shared_ptr lut); - ~WorldConverter(); - - bool hasNext() const; - void convertNext(); - - void write(); - - uint getTotalTasks() const; -}; - -#endif // FILES_WORLD_CONVERTER_H_ diff --git a/src/files/WorldConverter.hpp b/src/files/WorldConverter.hpp new file mode 100644 index 00000000..4e6159e0 --- /dev/null +++ b/src/files/WorldConverter.hpp @@ -0,0 +1,66 @@ +#ifndef FILES_WORLD_CONVERTER_HPP_ +#define FILES_WORLD_CONVERTER_HPP_ + +#include +#include +#include + +#include "../typedefs.hpp" +#include "../delegates.hpp" +#include "../interfaces/Task.hpp" + +namespace fs = std::filesystem; + +class Content; +class ContentLUT; +class WorldFiles; + +enum class convert_task_type { + region, player +}; + +struct convert_task { + convert_task_type type; + fs::path file; +}; + +class WorldConverter : public Task { + std::unique_ptr wfile; + std::shared_ptr const lut; + const Content* const content; + std::queue tasks; + runnable onComplete; + uint tasksDone = 0; + + void convertPlayer(fs::path file) const; + void convertRegion(fs::path file) const; +public: + WorldConverter( + fs::path folder, + const Content* content, + std::shared_ptr lut + ); + ~WorldConverter(); + + void convert(convert_task task) const; + void convertNext(); + void setOnComplete(runnable callback); + void write(); + + void update() override; + void terminate() override; + bool isActive() const override; + void waitForEnd() override; + uint getWorkTotal() const override; + uint getWorkDone() const override; + + static std::shared_ptr startTask( + fs::path folder, + const Content* content, + std::shared_ptr lut, + runnable onDone, + bool multithreading + ); +}; + +#endif // FILES_WORLD_CONVERTER_HPP_ diff --git a/src/files/WorldFiles.cpp b/src/files/WorldFiles.cpp index 86654f93..e92d8e62 100644 --- a/src/files/WorldFiles.cpp +++ b/src/files/WorldFiles.cpp @@ -1,26 +1,24 @@ -#include "WorldFiles.h" +#include "WorldFiles.hpp" -#include "rle.h" -#include "../window/Camera.h" -#include "../content/Content.h" -#include "../objects/Player.h" -#include "../physics/Hitbox.h" -#include "../voxels/voxel.h" -#include "../voxels/Block.h" -#include "../voxels/Chunk.h" -#include "../typedefs.h" -#include "../maths/voxmaths.h" -#include "../world/World.h" -#include "../lighting/Lightmap.h" - -#include "../coders/byte_utils.h" -#include "../util/data_io.h" -#include "../coders/json.h" -#include "../constants.h" -#include "../items/ItemDef.h" -#include "../items/Inventory.h" - -#include "../data/dynamic.h" +#include "../coders/byte_utils.hpp" +#include "../coders/json.hpp" +#include "../constants.hpp" +#include "../content/Content.hpp" +#include "../core_defs.hpp" +#include "../data/dynamic.hpp" +#include "../items/Inventory.hpp" +#include "../items/ItemDef.hpp" +#include "../lighting/Lightmap.hpp" +#include "../maths/voxmaths.hpp" +#include "../objects/Player.hpp" +#include "../physics/Hitbox.hpp" +#include "../typedefs.hpp" +#include "../util/data_io.hpp" +#include "../voxels/Block.hpp" +#include "../voxels/Chunk.hpp" +#include "../voxels/voxel.hpp" +#include "../window/Camera.hpp" +#include "../world/World.hpp" #include #include @@ -29,77 +27,18 @@ #include #include -#define REGION_FORMAT_MAGIC ".VOXREG" #define WORLD_FORMAT_MAGIC ".VOXWLD" -const size_t BUFFER_SIZE_UNKNOWN = -1; - -regfile::regfile(fs::path filename) : file(filename) { - if (file.length() < REGION_HEADER_SIZE) - throw std::runtime_error("incomplete region file header"); - char header[REGION_HEADER_SIZE]; - file.read(header, REGION_HEADER_SIZE); - - // avoid of use strcmp_s - if (std::string(header, strlen(REGION_FORMAT_MAGIC)) != REGION_FORMAT_MAGIC) { - throw std::runtime_error("invalid region file magic number"); - } - version = header[8]; - if (uint(version) > REGION_FORMAT_VERSION) { - throw illegal_region_format( - "region format "+std::to_string(version)+" is not supported"); - } +WorldFiles::WorldFiles(fs::path directory) : directory(directory), regions(directory) { } -WorldRegion::WorldRegion() { - chunksData = new ubyte*[REGION_CHUNKS_COUNT]{}; - sizes = new uint32_t[REGION_CHUNKS_COUNT]{}; -} - -WorldRegion::~WorldRegion() { - for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) { - delete[] chunksData[i]; - } - delete[] sizes; - delete[] chunksData; -} - -void WorldRegion::setUnsaved(bool unsaved) { - this->unsaved = unsaved; -} -bool WorldRegion::isUnsaved() const { - return unsaved; -} - -ubyte** WorldRegion::getChunks() const { - return chunksData; -} - -uint32_t* WorldRegion::getSizes() const { - return sizes; -} - -void WorldRegion::put(uint x, uint z, ubyte* data, uint32_t size) { - size_t chunk_index = z * REGION_SIZE + x; - delete[] chunksData[chunk_index]; - chunksData[chunk_index] = data; - sizes[chunk_index] = size; -} - -ubyte* WorldRegion::getChunkData(uint x, uint z) { - return chunksData[z * REGION_SIZE + x]; -} - -uint WorldRegion::getChunkDataSize(uint x, uint z) { - return sizes[z * REGION_SIZE + x]; -} - -WorldFiles::WorldFiles(fs::path directory, const DebugSettings& settings) - : directory(directory), - generatorTestMode(settings.generatorTestMode), - doWriteLights(settings.doWriteLights) +WorldFiles::WorldFiles(fs::path directory, const DebugSettings& settings) + : WorldFiles(directory) { - compressionBuffer = std::make_unique(CHUNK_DATA_LEN * 2); + generatorTestMode = settings.generatorTestMode.get(); + doWriteLights = settings.doWriteLights.get(); + regions.generatorTestMode = generatorTestMode; + regions.doWriteLights = doWriteLights; } WorldFiles::~WorldFiles() { @@ -110,172 +49,6 @@ void WorldFiles::createDirectories() { fs::create_directories(directory / fs::path("content")); } -WorldRegion* WorldFiles::getRegion(regionsmap& regions, int x, int z) { - auto found = regions.find(glm::ivec2(x, z)); - if (found == regions.end()) - return nullptr; - return found->second.get(); -} - -WorldRegion* WorldFiles::getOrCreateRegion(regionsmap& regions, int x, int z) { - WorldRegion* region = getRegion(regions, x, z); - if (region == nullptr) { - region = new WorldRegion(); - regions[glm::ivec2(x, z)].reset(region); - } - return region; -} - -ubyte* WorldFiles::compress(const ubyte* src, size_t srclen, size_t& len) { - ubyte* buffer = this->compressionBuffer.get(); - - len = extrle::encode(src, srclen, buffer); - ubyte* data = new ubyte[len]; - for (size_t i = 0; i < len; i++) { - data[i] = buffer[i]; - } - return data; -} - -ubyte* WorldFiles::decompress(const ubyte* src, size_t srclen, size_t dstlen) { - ubyte* decompressed = new ubyte[dstlen]; - extrle::decode(src, srclen, decompressed); - return decompressed; -} - -int WorldFiles::getVoxelRegionVersion(int x, int z) { - regfile* rf = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS), getRegionsFolder()); - if (rf == nullptr) { - return 0; - } - return rf->version; -} - -int WorldFiles::getVoxelRegionsVersion() { - fs::path regionsFolder = getRegionsFolder(); - if (!fs::is_directory(regionsFolder)) { - return REGION_FORMAT_VERSION; - } - for (auto file : fs::directory_iterator(regionsFolder)) { - int x; - int z; - if (!parseRegionFilename(file.path().stem().string(), x, z)) { - continue; - } - regfile* rf = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS), regionsFolder); - return rf->version; - } - return REGION_FORMAT_VERSION; -} - - -/// @brief Compress and store chunk voxels data in region -/// @param x chunk.x -/// @param z chunk.z -void WorldFiles::put(int x, int z, const ubyte* voxelData) { - int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); - - /* Writing Voxels */ { - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - region->setUnsaved(true); - size_t compressedSize; - ubyte* data = compress(voxelData, CHUNK_DATA_LEN, compressedSize); - region->put(localX, localZ, data, compressedSize); - } -} - -/// @brief Store chunk (voxels and lights) in region (existing or new) -void WorldFiles::put(Chunk* chunk){ - assert(chunk != nullptr); - - int regionX = floordiv(chunk->x, REGION_SIZE); - int regionZ = floordiv(chunk->z, REGION_SIZE); - int localX = chunk->x - (regionX * REGION_SIZE); - int localZ = chunk->z - (regionZ * REGION_SIZE); - - /* Writing voxels */ { - size_t compressedSize; - std::unique_ptr chunk_data (chunk->encode()); - ubyte* data = compress(chunk_data.get(), CHUNK_DATA_LEN, compressedSize); - - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - region->setUnsaved(true); - region->put(localX, localZ, data, compressedSize); - } - // Writing lights cache - if (doWriteLights && chunk->isLighted()) { - size_t compressedSize; - std::unique_ptr light_data (chunk->lightmap.encode()); - ubyte* data = compress(light_data.get(), LIGHTMAP_DATA_LEN, compressedSize); - - WorldRegion* region = getOrCreateRegion(lights, regionX, regionZ); - region->setUnsaved(true); - region->put(localX, localZ, data, compressedSize); - } - // Writing block inventories - if (!chunk->inventories.empty()){ - auto& inventories = chunk->inventories; - ByteBuilder builder; - builder.putInt32(inventories.size()); - for (auto& entry : inventories) { - builder.putInt32(entry.first); - auto map = entry.second->serialize(); - auto bytes = json::to_binary(map.get(), true); - builder.putInt32(bytes.size()); - builder.put(bytes.data(), bytes.size()); - } - WorldRegion* region = getOrCreateRegion(storages, regionX, regionZ); - region->setUnsaved(true); - - auto datavec = builder.data(); - uint datasize = builder.size(); - auto data = std::make_unique(datasize); - for (uint i = 0; i < datasize; i++) { - data[i] = datavec[i]; - } - region->put(localX, localZ, data.release(), datasize); - } -} - -fs::path WorldFiles::getRegionsFolder() const { - return directory/fs::path("regions"); -} - -fs::path WorldFiles::getLightsFolder() const { - return directory/fs::path("lights"); -} - -fs::path WorldFiles::getInventoriesFolder() const { - return directory/fs::path("inventories"); -} - -fs::path WorldFiles::getRegionFilename(int x, int z) const { - return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); -} - -/// @brief Extract X and Z from 'X_Z.bin' region file name. -/// @param name source region file name -/// @param x parsed X destination -/// @param z parsed Z destination -/// @return false if std::invalid_argument or std::out_of_range occurred -bool WorldFiles::parseRegionFilename(const std::string& name, int& x, int& z) { - size_t sep = name.find('_'); - if (sep == std::string::npos || sep == 0 || sep == name.length()-1) - return false; - try { - x = std::stoi(name.substr(0, sep)); - z = std::stoi(name.substr(sep+1)); - } catch (std::invalid_argument& err) { - return false; - } catch (std::out_of_range& err) { - return false; - } - return true; -} - fs::path WorldFiles::getPlayerFile() const { return directory/fs::path("player.json"); } @@ -292,229 +65,23 @@ fs::path WorldFiles::getPacksFile() const { return directory/fs::path("packs.list"); } -ubyte* WorldFiles::getChunk(int x, int z){ - return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true); -} - -/// @brief Get cached lights for chunk at x,z -/// @return lights data or nullptr -light_t* WorldFiles::getLights(int x, int z) { - std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true)); - if (data == nullptr) - return nullptr; - return Lightmap::decode(data.get()); -} - -chunk_inventories_map WorldFiles::fetchInventories(int x, int z) { - chunk_inventories_map inventories; - const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false); - if (data == nullptr) - return inventories; - ByteReader reader(data, BUFFER_SIZE_UNKNOWN); - int count = reader.getInt32(); - for (int i = 0; i < count; i++) { - uint index = reader.getInt32(); - uint size = reader.getInt32(); - auto map = json::from_binary(reader.pointer(), size); - reader.skip(size); - auto inv = std::make_shared(0, 0); - inv->deserialize(map.get()); - inventories[index] = inv; - } - return inventories; -} - -ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, - int x, int z, int layer, bool compression) { - int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); - - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); - - WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ); - ubyte* data = region->getChunkData(localX, localZ); - if (data == nullptr) { - uint32_t size; - data = readChunkData(x, z, size, folder, layer); - if (data != nullptr) { - region->put(localX, localZ, data, size); - } - } - if (data != nullptr) { - size_t size = region->getChunkDataSize(localX, localZ); - if (compression) { - return decompress(data, size, CHUNK_DATA_LEN); - } - return data; - } - return nullptr; -} - - -regfile* WorldFiles::getRegFile(glm::ivec3 coord, const fs::path& folder) { - const auto found = openRegFiles.find(coord); - if (found != openRegFiles.end()) { - return found->second.get(); - } - if (openRegFiles.size() == MAX_OPEN_REGION_FILES) { - // [todo] replace with closing the most unused region - auto iter = std::next(openRegFiles.begin(), rand() % openRegFiles.size()); - openRegFiles.erase(iter); - } - fs::path filename = folder / getRegionFilename(coord[0], coord[1]); - if (!fs::is_regular_file(filename)) { - return nullptr; - } - openRegFiles[coord] = std::make_unique(filename); - return openRegFiles[coord].get(); -} - -ubyte* WorldFiles::readChunkData(int x, - int z, - uint32_t& length, - fs::path folder, - int layer){ - if (generatorTestMode) - return nullptr; - - int regionX = floordiv(x, REGION_SIZE); - int regionZ = floordiv(z, REGION_SIZE); - int localX = x - (regionX * REGION_SIZE); - int localZ = z - (regionZ * REGION_SIZE); - int chunkIndex = localZ * REGION_SIZE + localX; - - glm::ivec3 coord(regionX, regionZ, layer); - regfile* rfile = WorldFiles::getRegFile(coord, folder); - if (rfile == nullptr) { - return nullptr; - } - files::rafile& file = rfile->file; - - size_t file_size = file.length(); - size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4; - - uint32_t offset; - file.seekg(table_offset + chunkIndex * 4); - file.read((char*)(&offset), 4); - offset = dataio::read_int32_big((const ubyte*)(&offset), 0); - - if (offset == 0){ - return nullptr; - } - - file.seekg(offset); - file.read((char*)(&offset), 4); - length = dataio::read_int32_big((const ubyte*)(&offset), 0); - ubyte* data = new ubyte[length]{}; - file.read((char*)data, length); - return data; -} - -/// @brief Read missing chunks data (null pointers) from region file -/// @param layer used as third part of openRegFiles map key -/// (see REGION_LAYER_* constants) -void WorldFiles::fetchChunks(WorldRegion* region, int x, int z, fs::path folder, int layer) { - ubyte** chunks = region->getChunks(); - uint32_t* sizes = region->getSizes(); - - for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { - int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE; - int chunk_z = (i / REGION_SIZE) + z * REGION_SIZE; - if (chunks[i] == nullptr) { - chunks[i] = readChunkData(chunk_x, chunk_z, sizes[i], folder, layer); - } - } -} - -/// @brief Write or rewrite region file -/// @param x region X -/// @param z region Z -/// @param layer used as third part of openRegFiles map key -/// (see REGION_LAYER_* constants) -void WorldFiles::writeRegion(int x, int z, WorldRegion* entry, fs::path folder, int layer){ - fs::path filename = folder/getRegionFilename(x, z); - - glm::ivec3 regcoord(x, z, layer); - if (getRegFile(regcoord, folder)) { - fetchChunks(entry, x, z, folder, layer); - openRegFiles.erase(regcoord); - } - - char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC; - header[8] = REGION_FORMAT_VERSION; - header[9] = 0; // flags - std::ofstream file(filename, std::ios::out | std::ios::binary); - file.write(header, REGION_HEADER_SIZE); - - size_t offset = REGION_HEADER_SIZE; - char intbuf[4]{}; - uint offsets[REGION_CHUNKS_COUNT]{}; - - ubyte** region = entry->getChunks(); - uint32_t* sizes = entry->getSizes(); - - for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { - ubyte* chunk = region[i]; - if (chunk == nullptr){ - offsets[i] = 0; - } else { - offsets[i] = offset; - - size_t compressedSize = sizes[i]; - dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0); - offset += 4 + compressedSize; - - file.write(intbuf, 4); - file.write((const char*)chunk, compressedSize); - } - } - for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { - dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0); - file.write(intbuf, 4); - } -} - -void WorldFiles::writeRegions(regionsmap& regions, const fs::path& folder, int layer) { - for (auto& it : regions){ - WorldRegion* region = it.second.get(); - if (region->getChunks() == nullptr || !region->isUnsaved()) - continue; - glm::ivec2 key = it.first; - writeRegion(key[0], key[1], region, folder, layer); - } -} - void WorldFiles::write(const World* world, const Content* content) { - fs::path regionsFolder = getRegionsFolder(); - fs::path lightsFolder = getLightsFolder(); - fs::path inventoriesFolder = getInventoriesFolder(); - - fs::create_directories(regionsFolder); - fs::create_directories(inventoriesFolder); - fs::create_directories(lightsFolder); - if (world) { writeWorldInfo(world); - writePacks(world); + if (!fs::exists(getPacksFile())) { + writePacks(world->getPacks()); + } } if (generatorTestMode) { return; } writeIndices(content->getIndices()); - writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS); - writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS); - writeRegions(storages, inventoriesFolder, REGION_LAYER_INVENTORIES); + regions.write(); } -void WorldFiles::writePacks(const World* world) { +void WorldFiles::writePacks(const std::vector& packs) { auto packsFile = getPacksFile(); - if (fs::is_regular_file(packsFile)) { - return; - } - - const auto& packs = world->getPacks(); std::stringstream ss; ss << "# autogenerated; do not modify\n"; for (const auto& pack : packs) { @@ -559,56 +126,15 @@ bool WorldFiles::readWorldInfo(World* world) { return true; } -void WorldFiles::addPack(const World* world, const std::string& id) { - fs::path file = getPacksFile(); - if (!fs::is_regular_file(file)) { - if (!fs::is_directory(directory)) { - fs::create_directories(directory); - } - writePacks(world); - } - auto packs = files::read_list(file); - packs.push_back(id); - - std::stringstream ss; - ss << "# autogenerated; do not modify\n"; - for (const auto& pack : packs) { - ss << pack << "\n"; - } - files::write_string(file, ss.str()); -} - -void WorldFiles::removePack(const World* world, const std::string& id) { - fs::path file = getPacksFile(); - if (!fs::is_regular_file(file)) { - if (!fs::is_directory(directory)) { - fs::create_directories(directory); - } - writePacks(world); - } - auto packs = files::read_list(file); - auto found = std::find(packs.begin(), packs.end(), id); - if (found != packs.end()) { - packs.erase(found); - } - - std::stringstream ss; - ss << "# autogenerated; do not modify\n"; - for (const auto& pack : packs) { - ss << pack << "\n"; - } - files::write_string(file, ss.str()); - - // erase invalid indices +static void erase_pack_indices(dynamic::Map* root, const std::string& id) { auto prefix = id+":"; - auto root = files::read_json(getIndicesFile()); auto blocks = root->list("blocks"); for (uint i = 0; i < blocks->size(); i++) { auto name = blocks->str(i); if (name.find(prefix) != 0) continue; auto value = blocks->getValueWriteable(i); - *value->value.str = "core:air"; + value->value = CORE_AIR; } auto items = root->list("items"); @@ -617,7 +143,18 @@ void WorldFiles::removePack(const World* world, const std::string& id) { if (name.find(prefix) != 0) continue; auto value = items->getValueWriteable(i); - *value->value.str = "core:empty"; + value->value = CORE_EMPTY; + } +} + +void WorldFiles::removeIndices(const std::vector& packs) { + auto root = files::read_json(getIndicesFile()); + for (const auto& id : packs) { + erase_pack_indices(root.get(), id); } files::write_json(getIndicesFile(), root.get()); } + +fs::path WorldFiles::getFolder() const { + return directory; +} diff --git a/src/files/WorldFiles.h b/src/files/WorldFiles.h deleted file mode 100644 index 1f66852d..00000000 --- a/src/files/WorldFiles.h +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef FILES_WORLDFILES_H_ -#define FILES_WORLDFILES_H_ - -#include -#include -#include -#include -#include - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include "glm/gtx/hash.hpp" - -#include "files.h" -#include "../typedefs.h" -#include "../settings.h" - -#include "../voxels/Chunk.h" - -inline constexpr uint REGION_HEADER_SIZE = 10; - -inline constexpr uint REGION_LAYER_VOXELS = 0; -inline constexpr uint REGION_LAYER_LIGHTS = 1; -inline constexpr uint REGION_LAYER_INVENTORIES = 2; - -inline constexpr uint REGION_SIZE_BIT = 5; -inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT)); -inline constexpr uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE)); -inline constexpr uint REGION_FORMAT_VERSION = 2; -inline constexpr uint WORLD_FORMAT_VERSION = 1; -inline constexpr uint MAX_OPEN_REGION_FILES = 16; - -class Player; -class Content; -class ContentIndices; -class World; - -namespace fs = std::filesystem; - -class illegal_region_format : public std::runtime_error { -public: - illegal_region_format(const std::string& message) - : std::runtime_error(message) {} -}; - -class WorldRegion { - ubyte** chunksData; - uint32_t* sizes; - bool unsaved = false; -public: - WorldRegion(); - ~WorldRegion(); - - void put(uint x, uint z, ubyte* data, uint32_t size); - ubyte* getChunkData(uint x, uint z); - uint getChunkDataSize(uint x, uint z); - - void setUnsaved(bool unsaved); - bool isUnsaved() const; - - ubyte** getChunks() const; - uint32_t* getSizes() const; -}; - -struct regfile { - files::rafile file; - int version; - - regfile(fs::path filename); -}; - -typedef std::unordered_map> regionsmap; - -class WorldFiles { - std::unordered_map> openRegFiles; - - void writeWorldInfo(const World* world); - fs::path getRegionFilename(int x, int y) const; - fs::path getWorldFile() const; - fs::path getIndicesFile() const; - fs::path getPacksFile() const; - - WorldRegion* getRegion(regionsmap& regions, int x, int z); - WorldRegion* getOrCreateRegion(regionsmap& regions, int x, int z); - - /// @brief Compress buffer with extrle - /// @param src source buffer - /// @param srclen length of the source buffer - /// @param len (out argument) length of result buffer - /// @return compressed bytes array - ubyte* compress(const ubyte* src, size_t srclen, size_t& len); - - /// @brief Decompress buffer with extrle - /// @param src compressed buffer - /// @param srclen length of compressed buffer - /// @param dstlen max expected length of source buffer - /// @return decompressed bytes array - ubyte* decompress(const ubyte* src, size_t srclen, size_t dstlen); - - ubyte* readChunkData(int x, int y, uint32_t& length, fs::path folder, int layer); - - void fetchChunks(WorldRegion* region, int x, int y, fs::path folder, int layer); - - void writeRegions(regionsmap& regions, const fs::path& folder, int layer); - - ubyte* getData(regionsmap& regions, const fs::path& folder, int x, int z, int layer, bool compression); - - regfile* getRegFile(glm::ivec3 coord, const fs::path& folder); - - fs::path getLightsFolder() const; - fs::path getInventoriesFolder() const; -public: - static bool parseRegionFilename(const std::string& name, int& x, int& y); - fs::path getRegionsFolder() const; - fs::path getPlayerFile() const; - - regionsmap regions; - regionsmap storages; - regionsmap lights; - fs::path directory; - std::unique_ptr compressionBuffer; - bool generatorTestMode; - bool doWriteLights; - - WorldFiles(fs::path directory, const DebugSettings& settings); - ~WorldFiles(); - - void createDirectories(); - - void put(Chunk* chunk); - void put(int x, int z, const ubyte* voxelData); - - int getVoxelRegionVersion(int x, int z); - int getVoxelRegionsVersion(); - - ubyte* getChunk(int x, int z); - light_t* getLights(int x, int z); - chunk_inventories_map fetchInventories(int x, int z); - - bool readWorldInfo(World* world); - - void writeRegion(int x, int y, WorldRegion* entry, fs::path file, int layer); - - /// @brief Write all unsaved data to world files - /// @param world target world - /// @param content world content - void write(const World* world, const Content* content); - - void writePacks(const World* world); - void writeIndices(const ContentIndices* indices); - - /// @brief Append pack to the packs list without duplicate check and - /// dependencies resolve - /// @param world target world - /// @param id pack id - void addPack(const World* world, const std::string& id); - - /// @brief Remove pack from the list (does not remove indices) - /// @param world target world - /// @param id pack id - void removePack(const World* world, const std::string& id); - - static const inline std::string WORLD_FILE = "world.json"; -}; - -#endif /* FILES_WORLDFILES_H_ */ diff --git a/src/files/WorldFiles.hpp b/src/files/WorldFiles.hpp new file mode 100644 index 00000000..7247b246 --- /dev/null +++ b/src/files/WorldFiles.hpp @@ -0,0 +1,76 @@ +#ifndef FILES_WORLD_FILES_HPP_ +#define FILES_WORLD_FILES_HPP_ + +#include "WorldRegions.hpp" + +#include "files.hpp" +#include "../typedefs.hpp" +#include "../settings.hpp" +#include "../content/ContentPack.hpp" +#include "../voxels/Chunk.hpp" + +#include +#include +#include +#include + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/gtx/hash.hpp" + +inline constexpr uint WORLD_FORMAT_VERSION = 1; + +class Player; +class Content; +class ContentIndices; +class World; + +namespace fs = std::filesystem; + +class WorldFiles { + fs::path directory; + WorldRegions regions; + + bool generatorTestMode = false; + bool doWriteLights = true; + + fs::path getWorldFile() const; + fs::path getIndicesFile() const; + fs::path getPacksFile() const; + + void writeWorldInfo(const World* world); + void writeIndices(const ContentIndices* indices); +public: + WorldFiles(fs::path directory); + WorldFiles(fs::path directory, const DebugSettings& settings); + ~WorldFiles(); + + fs::path getPlayerFile() const; + void createDirectories(); + + bool readWorldInfo(World* world); + + /// @brief Write all unsaved data to world files + /// @param world target world + /// @param content world content + void write(const World* world, const Content* content); + + void writePacks(const std::vector& packs); + + void removeIndices(const std::vector& packs); + + /// @return world folder + fs::path getFolder() const; + + static const inline std::string WORLD_FILE = "world.json"; + + WorldRegions& getRegions() { + return regions; + } + + bool doesWriteLights() const { + return doWriteLights; + } +}; + +#endif // FILES_WORLD_FILES_HPP_ diff --git a/src/files/WorldRegions.cpp b/src/files/WorldRegions.cpp new file mode 100644 index 00000000..151e8914 --- /dev/null +++ b/src/files/WorldRegions.cpp @@ -0,0 +1,479 @@ +#include "WorldRegions.hpp" + +#include "../coders/byte_utils.hpp" +#include "../coders/rle.hpp" +#include "../data/dynamic.hpp" +#include "../items/Inventory.hpp" +#include "../maths/voxmaths.hpp" +#include "../util/data_io.hpp" + +#include + +#define REGION_FORMAT_MAGIC ".VOXREG" + +regfile::regfile(fs::path filename) : file(filename) { + if (file.length() < REGION_HEADER_SIZE) + throw std::runtime_error("incomplete region file header"); + char header[REGION_HEADER_SIZE]; + file.read(header, REGION_HEADER_SIZE); + + // avoid of use strcmp_s + if (std::string(header, strlen(REGION_FORMAT_MAGIC)) != REGION_FORMAT_MAGIC) { + throw std::runtime_error("invalid region file magic number"); + } + version = header[8]; + if (uint(version) > REGION_FORMAT_VERSION) { + throw illegal_region_format( + "region format "+std::to_string(version)+" is not supported" + ); + } +} + +std::unique_ptr regfile::read(int index, uint32_t& length) { + size_t file_size = file.length(); + size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4; + + uint32_t offset; + file.seekg(table_offset + index * 4); + file.read((char*)(&offset), 4); + offset = dataio::read_int32_big((const ubyte*)(&offset), 0); + if (offset == 0){ + return nullptr; + } + + file.seekg(offset); + file.read((char*)(&offset), 4); + length = dataio::read_int32_big((const ubyte*)(&offset), 0); + auto data = std::make_unique(length); + file.read((char*)data.get(), length); + return data; +} + +WorldRegion::WorldRegion() { + chunksData = new ubyte*[REGION_CHUNKS_COUNT]{}; + sizes = new uint32_t[REGION_CHUNKS_COUNT]{}; +} + +WorldRegion::~WorldRegion() { + for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) { + delete[] chunksData[i]; + } + delete[] sizes; + delete[] chunksData; +} + +void WorldRegion::setUnsaved(bool unsaved) { + this->unsaved = unsaved; +} +bool WorldRegion::isUnsaved() const { + return unsaved; +} + +ubyte** WorldRegion::getChunks() const { + return chunksData; +} + +uint32_t* WorldRegion::getSizes() const { + return sizes; +} + +void WorldRegion::put(uint x, uint z, ubyte* data, uint32_t size) { + size_t chunk_index = z * REGION_SIZE + x; + delete[] chunksData[chunk_index]; + chunksData[chunk_index] = data; + sizes[chunk_index] = size; +} + +ubyte* WorldRegion::getChunkData(uint x, uint z) { + return chunksData[z * REGION_SIZE + x]; +} + +uint WorldRegion::getChunkDataSize(uint x, uint z) { + return sizes[z * REGION_SIZE + x]; +} + +WorldRegions::WorldRegions(fs::path directory) : directory(directory) { + for (uint i = 0; i < sizeof(layers)/sizeof(RegionsLayer); i++) { + layers[i].layer = i; + } + layers[REGION_LAYER_VOXELS].folder = directory/fs::path("regions"); + layers[REGION_LAYER_LIGHTS].folder = directory/fs::path("lights"); + layers[REGION_LAYER_INVENTORIES].folder = directory/fs::path("inventories"); +} + +WorldRegions::~WorldRegions() { +} + +WorldRegion* WorldRegions::getRegion(int x, int z, int layer) { + RegionsLayer& regions = layers[layer]; + std::lock_guard lock(regions.mutex); + auto found = regions.regions.find(glm::ivec2(x, z)); + if (found == regions.regions.end()) + return nullptr; + return found->second.get(); +} + +WorldRegion* WorldRegions::getOrCreateRegion(int x, int z, int layer) { + RegionsLayer& regions = layers[layer]; + WorldRegion* region = getRegion(x, z, layer); + if (region == nullptr) { + std::lock_guard lock(regions.mutex); + region = new WorldRegion(); + regions.regions[glm::ivec2(x, z)].reset(region); + } + return region; +} + +std::unique_ptr WorldRegions::compress(const ubyte* src, size_t srclen, size_t& len) { + auto buffer = bufferPool.get(); + ubyte* bytes = buffer.get(); + + len = extrle::encode(src, srclen, bytes); + auto data = std::make_unique(len); + for (size_t i = 0; i < len; i++) { + data[i] = bytes[i]; + } + return data; +} + +std::unique_ptr WorldRegions::decompress(const ubyte* src, size_t srclen, size_t dstlen) { + auto decompressed = std::make_unique(dstlen); + extrle::decode(src, srclen, decompressed.get()); + return decompressed; +} + +inline void calc_reg_coords( + int x, int z, int& regionX, int& regionZ, int& localX, int& localZ +) { + regionX = floordiv(x, REGION_SIZE); + regionZ = floordiv(z, REGION_SIZE); + localX = x - (regionX * REGION_SIZE); + localZ = z - (regionZ * REGION_SIZE); +} + +std::unique_ptr WorldRegions::readChunkData( + int x, + int z, + uint32_t& length, + regfile* rfile +){ + int regionX, regionZ, localX, localZ; + calc_reg_coords(x, z, regionX, regionZ, localX, localZ); + int chunkIndex = localZ * REGION_SIZE + localX; + return rfile->read(chunkIndex, length); +} + +/// @brief Read missing chunks data (null pointers) from region file +void WorldRegions::fetchChunks(WorldRegion* region, int x, int z, regfile* file) { + ubyte** chunks = region->getChunks(); + uint32_t* sizes = region->getSizes(); + + for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { + int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE; + int chunk_z = (i / REGION_SIZE) + z * REGION_SIZE; + if (chunks[i] == nullptr) { + chunks[i] = readChunkData(chunk_x, chunk_z, sizes[i], file).release(); + } + } +} + +ubyte* WorldRegions::getData( + int x, int z, int layer, + uint32_t& size +) { + if (generatorTestMode) { + return nullptr; + } + int regionX, regionZ, localX, localZ; + calc_reg_coords(x, z, regionX, regionZ, localX, localZ); + + WorldRegion* region = getOrCreateRegion(regionX, regionZ, layer); + ubyte* data = region->getChunkData(localX, localZ); + if (data == nullptr) { + auto regfile = getRegFile(glm::ivec3(regionX, regionZ, layer)); + if (regfile != nullptr) { + data = readChunkData(x, z, size, regfile.get()).release(); + } + if (data != nullptr) { + region->put(localX, localZ, data, size); + } + } + if (data != nullptr) { + size = region->getChunkDataSize(localX, localZ); + return data; + } + return nullptr; +} + +std::shared_ptr WorldRegions::useRegFile(glm::ivec3 coord) { + auto* file = openRegFiles[coord].get(); + file->inUse = true; + return std::shared_ptr(file, [this](regfile* ptr) { + ptr->inUse = false; + regFilesCv.notify_one(); + }); +} + +void WorldRegions::closeRegFile(glm::ivec3 coord) { + openRegFiles.erase(coord); + regFilesCv.notify_one(); +} + +// Marks regfile as used and unmarks when shared_ptr dies +std::shared_ptr WorldRegions::getRegFile(glm::ivec3 coord, bool create) { + { + std::lock_guard lock(regFilesMutex); + const auto found = openRegFiles.find(coord); + if (found != openRegFiles.end()) { + if (found->second->inUse) { + throw std::runtime_error("regfile is currently in use"); + } + return useRegFile(found->first); + } + } + if (create) { + return createRegFile(coord); + } + return nullptr; +} + +std::shared_ptr WorldRegions::createRegFile(glm::ivec3 coord) { + fs::path file = layers[coord[2]].folder/getRegionFilename(coord[0], coord[1]); + if (!fs::exists(file)) { + return nullptr; + } + if (openRegFiles.size() == MAX_OPEN_REGION_FILES) { + std::unique_lock lock(regFilesMutex); + while (true) { + bool closed = false; + // FIXME: bad choosing algorithm + for (auto& entry : openRegFiles) { + if (!entry.second->inUse) { + closeRegFile(entry.first); + closed = true; + break; + } + } + if (closed) { + break; + } + // notified when any regfile gets out of use or closed + regFilesCv.wait(lock); + } + openRegFiles[coord] = std::make_unique(file); + return useRegFile(coord); + } else { + std::lock_guard lock(regFilesMutex); + openRegFiles[coord] = std::make_unique(file); + return useRegFile(coord); + } +} + +fs::path WorldRegions::getRegionFilename(int x, int z) const { + return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin"); +} + +void WorldRegions::writeRegion(int x, int z, int layer, WorldRegion* entry){ + fs::path filename = layers[layer].folder/getRegionFilename(x, z); + + glm::ivec3 regcoord(x, z, layer); + if (auto regfile = getRegFile(regcoord, false)) { + fetchChunks(entry, x, z, regfile.get()); + + std::lock_guard lock(regFilesMutex); + closeRegFile(regcoord); + } + + char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC; + header[8] = REGION_FORMAT_VERSION; + header[9] = 0; // flags + std::ofstream file(filename, std::ios::out | std::ios::binary); + file.write(header, REGION_HEADER_SIZE); + + size_t offset = REGION_HEADER_SIZE; + char intbuf[4]{}; + uint offsets[REGION_CHUNKS_COUNT]{}; + + ubyte** region = entry->getChunks(); + uint32_t* sizes = entry->getSizes(); + + for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { + ubyte* chunk = region[i]; + if (chunk == nullptr){ + offsets[i] = 0; + } else { + offsets[i] = offset; + + size_t compressedSize = sizes[i]; + dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0); + offset += 4 + compressedSize; + + file.write(intbuf, 4); + file.write((const char*)chunk, compressedSize); + } + } + for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) { + dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0); + file.write(intbuf, 4); + } +} + +void WorldRegions::writeRegions(int layer) { + for (auto& it : layers[layer].regions){ + WorldRegion* region = it.second.get(); + if (region->getChunks() == nullptr || !region->isUnsaved()) + continue; + glm::ivec2 key = it.first; + writeRegion(key[0], key[1], layer, region); + } +} + +void WorldRegions::put(int x, int z, int layer, std::unique_ptr data, size_t size, bool rle) { + if (rle) { + size_t compressedSize; + auto compressed = compress(data.get(), size, compressedSize); + put(x, z, layer, std::move(compressed), compressedSize, false); + return; + } + int regionX, regionZ, localX, localZ; + calc_reg_coords(x, z, regionX, regionZ, localX, localZ); + + WorldRegion* region = getOrCreateRegion(regionX, regionZ, layer); + region->setUnsaved(true); + region->put(localX, localZ, data.release(), size); +} + +static std::unique_ptr write_inventories(Chunk* chunk, uint& datasize) { + auto& inventories = chunk->inventories; + ByteBuilder builder; + builder.putInt32(inventories.size()); + for (auto& entry : inventories) { + builder.putInt32(entry.first); + auto map = entry.second->serialize(); + auto bytes = json::to_binary(map.get(), true); + builder.putInt32(bytes.size()); + builder.put(bytes.data(), bytes.size()); + } + auto datavec = builder.data(); + datasize = builder.size(); + auto data = std::make_unique(datasize); + for (uint i = 0; i < datasize; i++) { + data[i] = datavec[i]; + } + return data; +} + +/// @brief Store chunk (voxels and lights) in region (existing or new) +void WorldRegions::put(Chunk* chunk){ + assert(chunk != nullptr); + + int regionX, regionZ, localX, localZ; + calc_reg_coords(chunk->x, chunk->z, regionX, regionZ, localX, localZ); + + put(chunk->x, chunk->z, REGION_LAYER_VOXELS, + chunk->encode(), CHUNK_DATA_LEN, true); + + // Writing lights cache + if (doWriteLights && chunk->isLighted()) { + put(chunk->x, chunk->z, REGION_LAYER_LIGHTS, + chunk->lightmap.encode(), LIGHTMAP_DATA_LEN, true); + } + // Writing block inventories + if (!chunk->inventories.empty()){ + uint datasize; + auto data = write_inventories(chunk, datasize); + put(chunk->x, chunk->z, REGION_LAYER_INVENTORIES, + std::move(data), datasize, false); + } +} + +std::unique_ptr WorldRegions::getChunk(int x, int z){ + uint32_t size; + auto* data = getData(x, z, REGION_LAYER_VOXELS, size); + if (data == nullptr) { + return nullptr; + } + return decompress(data, size, CHUNK_DATA_LEN); +} + +/// @brief Get cached lights for chunk at x,z +/// @return lights data or nullptr +std::unique_ptr WorldRegions::getLights(int x, int z) { + uint32_t size; + auto* bytes = getData(x, z, REGION_LAYER_LIGHTS, size); + if (bytes == nullptr) + return nullptr; + auto data = decompress(bytes, size, LIGHTMAP_DATA_LEN); + return Lightmap::decode(data.get()); +} + +chunk_inventories_map WorldRegions::fetchInventories(int x, int z) { + chunk_inventories_map inventories; + uint32_t bytesSize; + const ubyte* data = getData(x, z, REGION_LAYER_INVENTORIES, bytesSize); + if (data == nullptr) + return inventories; + ByteReader reader(data, bytesSize); + int count = reader.getInt32(); + for (int i = 0; i < count; i++) { + uint index = reader.getInt32(); + uint size = reader.getInt32(); + auto map = json::from_binary(reader.pointer(), size); + reader.skip(size); + auto inv = std::make_shared(0, 0); + inv->deserialize(map.get()); + inventories[index] = inv; + } + return inventories; +} + +void WorldRegions::processRegionVoxels(int x, int z, regionproc func) { + if (getRegion(x, z, REGION_LAYER_VOXELS)) { + throw std::runtime_error("not implemented for in-memory regions"); + } + auto regfile = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS)); + if (regfile == nullptr) { + throw std::runtime_error("could not open region file"); + } + for (uint cz = 0; cz < REGION_SIZE; cz++) { + for (uint cx = 0; cx < REGION_SIZE; cx++) { + int gx = cx + x * REGION_SIZE; + int gz = cz + z * REGION_SIZE; + uint32_t length; + auto data = readChunkData(gx, gz, length, regfile.get()); + if (data == nullptr) + continue; + data = decompress(data.get(), length, CHUNK_DATA_LEN); + if (func(data.get())) { + put(gx, gz, REGION_LAYER_VOXELS, std::move(data), CHUNK_DATA_LEN, true); + } + } + } +} + +fs::path WorldRegions::getRegionsFolder(int layer) const { + return layers[layer].folder; +} + + +void WorldRegions::write() { + for (auto& layer : layers) { + fs::create_directories(layer.folder); + writeRegions(layer.layer); + } +} + +bool WorldRegions::parseRegionFilename(const std::string& name, int& x, int& z) { + size_t sep = name.find('_'); + if (sep == std::string::npos || sep == 0 || sep == name.length()-1) + return false; + try { + x = std::stoi(name.substr(0, sep)); + z = std::stoi(name.substr(sep+1)); + } catch (std::invalid_argument& err) { + return false; + } catch (std::out_of_range& err) { + return false; + } + return true; +} diff --git a/src/files/WorldRegions.hpp b/src/files/WorldRegions.hpp new file mode 100644 index 00000000..ae727c09 --- /dev/null +++ b/src/files/WorldRegions.hpp @@ -0,0 +1,165 @@ +#ifndef FILES_WORLD_REGIONS_HPP_ +#define FILES_WORLD_REGIONS_HPP_ + +#include "files.hpp" +#include "../typedefs.hpp" +#include "../util/BufferPool.hpp" +#include "../voxels/Chunk.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/gtx/hash.hpp" + +namespace fs = std::filesystem; + +inline constexpr uint REGION_HEADER_SIZE = 10; + +inline constexpr uint REGION_LAYER_VOXELS = 0; +inline constexpr uint REGION_LAYER_LIGHTS = 1; +inline constexpr uint REGION_LAYER_INVENTORIES = 2; + +inline constexpr uint REGION_SIZE_BIT = 5; +inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT)); +inline constexpr uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE)); +inline constexpr uint REGION_FORMAT_VERSION = 2; +inline constexpr uint MAX_OPEN_REGION_FILES = 16; + +class illegal_region_format : public std::runtime_error { +public: + illegal_region_format(const std::string& message) + : std::runtime_error(message) {} +}; + +class WorldRegion { + ubyte** chunksData; + uint32_t* sizes; + bool unsaved = false; +public: + WorldRegion(); + ~WorldRegion(); + + void put(uint x, uint z, ubyte* data, uint32_t size); + ubyte* getChunkData(uint x, uint z); + uint getChunkDataSize(uint x, uint z); + + void setUnsaved(bool unsaved); + bool isUnsaved() const; + + ubyte** getChunks() const; + uint32_t* getSizes() const; +}; + +struct regfile { + files::rafile file; + int version; + bool inUse = false; + + regfile(fs::path filename); + regfile(const regfile&) = delete; + + std::unique_ptr read(int index, uint32_t& length); +}; + +using regionsmap = std::unordered_map>; +using regionproc = std::function; + +struct RegionsLayer { + int layer; + fs::path folder; + regionsmap regions; + std::mutex mutex; +}; + +class WorldRegions { + fs::path directory; + std::unordered_map> openRegFiles; + std::mutex regFilesMutex; + std::condition_variable regFilesCv; + RegionsLayer layers[3] {}; + util::BufferPool bufferPool { + std::max(CHUNK_DATA_LEN, LIGHTMAP_DATA_LEN) * 2 + }; + + WorldRegion* getRegion(int x, int z, int layer); + WorldRegion* getOrCreateRegion(int x, int z, int layer); + + /// @brief Compress buffer with extrle + /// @param src source buffer + /// @param srclen length of the source buffer + /// @param len (out argument) length of result buffer + /// @return compressed bytes array + std::unique_ptr compress(const ubyte* src, size_t srclen, size_t& len); + + /// @brief Decompress buffer with extrle + /// @param src compressed buffer + /// @param srclen length of compressed buffer + /// @param dstlen max expected length of source buffer + /// @return decompressed bytes array + std::unique_ptr decompress(const ubyte* src, size_t srclen, size_t dstlen); + + std::unique_ptr readChunkData(int x, int y, uint32_t& length, regfile* file); + + void fetchChunks(WorldRegion* region, int x, int y, regfile* file); + + ubyte* getData(int x, int z, int layer, uint32_t& size); + + std::shared_ptr getRegFile(glm::ivec3 coord, bool create=true); + void closeRegFile(glm::ivec3 coord); + std::shared_ptr useRegFile(glm::ivec3 coord); + std::shared_ptr createRegFile(glm::ivec3 coord); + + fs::path getRegionFilename(int x, int y) const; + + void writeRegions(int layer); + + /// @brief Write or rewrite region file + /// @param x region X + /// @param z region Z + /// @param layer regions layer + void writeRegion(int x, int y, int layer, WorldRegion* entry); +public: + bool generatorTestMode = false; + bool doWriteLights = true; + + WorldRegions(fs::path directory); + WorldRegions(const WorldRegions&) = delete; + ~WorldRegions(); + + /// @brief Put all chunk data to regions + void put(Chunk* chunk); + + /// @brief Store data in specified region + /// @param x chunk.x + /// @param z chunk.z + /// @param layer regions layer + /// @param data target data + /// @param size data size + /// @param rle compress with ext-RLE + void put(int x, int z, int layer, std::unique_ptr data, size_t size, bool rle); + + std::unique_ptr getChunk(int x, int z); + std::unique_ptr getLights(int x, int z); + chunk_inventories_map fetchInventories(int x, int z); + + void processRegionVoxels(int x, int z, regionproc func); + + fs::path getRegionsFolder(int layer) const; + + void write(); + + /// @brief Extract X and Z from 'X_Z.bin' region file name. + /// @param name source region file name + /// @param x parsed X destination + /// @param z parsed Z destination + /// @return false if std::invalid_argument or std::out_of_range occurred + static bool parseRegionFilename(const std::string& name, int& x, int& y); +}; + +#endif // FILES_WORLD_REGIONS_HPP_ diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp index 2f716f97..f14828b3 100644 --- a/src/files/engine_paths.cpp +++ b/src/files/engine_paths.cpp @@ -1,13 +1,17 @@ -#include "engine_paths.h" +#include "engine_paths.hpp" #include #include #include +#include -#include "../typedefs.h" -#include "WorldFiles.h" +#include "../util/stringutil.hpp" +#include "../typedefs.hpp" +#include "WorldFiles.hpp" const fs::path SCREENSHOTS_FOLDER {"screenshots"}; +const fs::path CONTROLS_FILE {"controls.json"}; +const fs::path SETTINGS_FILE {"settings.toml"}; fs::path EnginePaths::getUserfiles() const { return userfiles; @@ -48,6 +52,18 @@ fs::path EnginePaths::getWorldFolder() { return worldFolder; } +fs::path EnginePaths::getWorldFolder(const std::string& name) { + return getWorldsFolder()/fs::path(name); +} + +fs::path EnginePaths::getControlsFile() { + return userfiles/fs::path(CONTROLS_FILE); +} + +fs::path EnginePaths::getSettingsFile() { + return userfiles/fs::path(SETTINGS_FILE); +} + std::vector EnginePaths::scanForWorlds() { std::vector folders; @@ -148,14 +164,14 @@ fs::path EnginePaths::resolve(std::string path) { throw files_access_error("unknown entry point '"+prefix+"'"); } -ResPaths::ResPaths(fs::path mainRoot, std::vector roots) +ResPaths::ResPaths(fs::path mainRoot, std::vector> roots) : mainRoot(mainRoot), roots(roots) { } fs::path ResPaths::find(const std::string& filename) const { for (int i = roots.size()-1; i >= 0; i--) { auto& root = roots[i]; - fs::path file = root / fs::u8path(filename); + fs::path file = root.second / fs::u8path(filename); if (fs::exists(file)) { return file; } @@ -163,11 +179,25 @@ fs::path ResPaths::find(const std::string& filename) const { return mainRoot / fs::u8path(filename); } +std::string ResPaths::findRaw(const std::string& filename) const { + for (int i = roots.size()-1; i >= 0; i--) { + auto& root = roots[i]; + if (fs::exists(root.second / fs::path(filename))) { + return root.first+":"+filename; + } + } + auto resDir = mainRoot; + if (fs::exists(resDir / fs::path(filename))) { + return "core:"+filename; + } + throw std::runtime_error("could not to find file "+util::quote(filename)); +} + std::vector ResPaths::listdir(const std::string& folderName) const { std::vector entries; for (int i = roots.size()-1; i >= 0; i--) { auto& root = roots[i]; - fs::path folder = root / fs::u8path(folderName); + fs::path folder = root.second / fs::u8path(folderName); if (!fs::is_directory(folder)) continue; for (const auto& entry : fs::directory_iterator(folder)) { diff --git a/src/files/engine_paths.h b/src/files/engine_paths.hpp similarity index 68% rename from src/files/engine_paths.h rename to src/files/engine_paths.hpp index cc9ee863..41c57e6e 100644 --- a/src/files/engine_paths.h +++ b/src/files/engine_paths.hpp @@ -1,12 +1,12 @@ -#ifndef FILES_ENGINE_PATHS_H_ -#define FILES_ENGINE_PATHS_H_ +#ifndef FILES_ENGINE_PATHS_HPP_ +#define FILES_ENGINE_PATHS_HPP_ #include #include #include #include -#include "../content/ContentPack.h" +#include "../content/ContentPack.hpp" namespace fs = std::filesystem; @@ -27,6 +27,9 @@ public: fs::path getScreenshotFile(std::string ext); fs::path getWorldsFolder(); fs::path getWorldFolder(); + fs::path getWorldFolder(const std::string& name); + fs::path getControlsFile(); + fs::path getSettingsFile(); bool isWorldNameUsed(std::string name); void setUserfiles(fs::path folder); @@ -41,15 +44,18 @@ public: class ResPaths { fs::path mainRoot; - std::vector roots; + std::vector> roots; public: - ResPaths(fs::path mainRoot, - std::vector roots); + ResPaths( + fs::path mainRoot, + std::vector> roots + ); fs::path find(const std::string& filename) const; + std::string findRaw(const std::string& filename) const; std::vector listdir(const std::string& folder) const; const fs::path& getMainRoot() const; }; -#endif // FILES_ENGINE_PATHS_H_ +#endif // FILES_ENGINE_PATHS_HPP_ diff --git a/src/files/files.cpp b/src/files/files.cpp index 59fa44b1..3cfffe76 100644 --- a/src/files/files.cpp +++ b/src/files/files.cpp @@ -1,14 +1,15 @@ -#include "files.h" +#include "files.hpp" + +#include "../coders/json.hpp" +#include "../coders/gzip.hpp" +#include "../util/stringutil.hpp" +#include "../data/dynamic.hpp" #include #include #include #include #include -#include "../coders/json.h" -#include "../coders/gzip.h" -#include "../util/stringutil.h" -#include "../data/dynamic.h" namespace fs = std::filesystem; @@ -34,64 +35,64 @@ void files::rafile::read(char* buffer, std::streamsize size) { } bool files::write_bytes(fs::path filename, const ubyte* data, size_t size) { - std::ofstream output(filename, std::ios::binary); - if (!output.is_open()) - return false; - output.write((const char*)data, size); - output.close(); - return true; + std::ofstream output(filename, std::ios::binary); + if (!output.is_open()) + return false; + output.write((const char*)data, size); + output.close(); + return true; } uint files::append_bytes(fs::path filename, const ubyte* data, size_t size) { - std::ofstream output(filename, std::ios::binary | std::ios::app); - if (!output.is_open()) - return 0; - uint position = output.tellp(); - output.write((const char*)data, size); - output.close(); - return position; + std::ofstream output(filename, std::ios::binary | std::ios::app); + if (!output.is_open()) + return 0; + uint position = output.tellp(); + output.write((const char*)data, size); + output.close(); + return position; } bool files::read(fs::path filename, char* data, size_t size) { - std::ifstream output(filename, std::ios::binary); - if (!output.is_open()) - return false; - output.read(data, size); - output.close(); - return true; + std::ifstream output(filename, std::ios::binary); + if (!output.is_open()) + return false; + output.read(data, size); + output.close(); + return true; } -ubyte* files::read_bytes(fs::path filename, size_t& length) { - std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) - return nullptr; - input.seekg(0, std::ios_base::end); - length = input.tellg(); - input.seekg(0, std::ios_base::beg); +std::unique_ptr files::read_bytes(fs::path filename, size_t& length) { + std::ifstream input(filename, std::ios::binary); + if (!input.is_open()) + return nullptr; + input.seekg(0, std::ios_base::end); + length = input.tellg(); + input.seekg(0, std::ios_base::beg); - std::unique_ptr data(new char[length]); - input.read(data.get(), length); - input.close(); - return (ubyte*)data.release(); + auto data = std::make_unique(length); + input.read((char*)data.get(), length); + input.close(); + return data; } std::string files::read_string(fs::path filename) { - size_t size; - std::unique_ptr bytes (read_bytes(filename, size)); - if (bytes == nullptr) { - throw std::runtime_error("could not to load file '"+ - filename.string()+"'"); - } - return std::string((const char*)bytes.get(), size); + size_t size; + std::unique_ptr bytes (read_bytes(filename, size)); + if (bytes == nullptr) { + throw std::runtime_error("could not to load file '"+ + filename.string()+"'"); + } + return std::string((const char*)bytes.get(), size); } bool files::write_string(fs::path filename, const std::string content) { - std::ofstream file(filename); - if (!file) { - return false; - } - file << content; - return true; + std::ofstream file(filename); + if (!file) { + return false; + } + file << content; + return true; } bool files::write_json(fs::path filename, const dynamic::Map* obj, bool nice) { @@ -104,11 +105,11 @@ bool files::write_binary_json(fs::path filename, const dynamic::Map* obj, bool c } std::unique_ptr files::read_json(fs::path filename) { - std::string text = files::read_string(filename); - try { - auto obj = json::parse(filename.string(), text); + std::string text = files::read_string(filename); + try { + auto obj = json::parse(filename.string(), text); return obj; - } catch (const parsing_error& error) { + } catch (const parsing_error& error) { std::cerr << error.errorLog() << std::endl; throw std::runtime_error("could not to parse "+filename.string()); } @@ -123,19 +124,19 @@ std::unique_ptr files::read_binary_json(fs::path file) { } std::vector files::read_list(fs::path filename) { - std::ifstream file(filename); - if (!file) { - throw std::runtime_error("could not to open file "+filename.u8string()); - } - std::vector lines; - std::string line; - while (std::getline(file, line)) { + std::ifstream file(filename); + if (!file) { + throw std::runtime_error("could not to open file "+filename.u8string()); + } + std::vector lines; + std::string line; + while (std::getline(file, line)) { util::trim(line); - if (line.length() == 0) - continue; - if (line[0] == '#') - continue; - lines.push_back(line); - } - return lines; + if (line.length() == 0) + continue; + if (line[0] == '#') + continue; + lines.push_back(line); + } + return lines; } diff --git a/src/files/files.h b/src/files/files.hpp similarity index 65% rename from src/files/files.h rename to src/files/files.hpp index eba79a07..87a6e3a8 100644 --- a/src/files/files.h +++ b/src/files/files.hpp @@ -1,12 +1,12 @@ -#ifndef FILES_FILES_H_ -#define FILES_FILES_H_ +#ifndef FILES_FILES_HPP_ +#define FILES_FILES_HPP_ #include #include #include #include #include -#include "../typedefs.h" +#include "../typedefs.hpp" namespace fs = std::filesystem; @@ -31,20 +31,20 @@ namespace files { /// @param file target file /// @param data data bytes array /// @param size size of data bytes array - extern bool write_bytes(fs::path file, const ubyte* data, size_t size); + bool write_bytes(fs::path file, const ubyte* data, size_t size); /// @brief Append bytes array to the file without any extra data /// @param file target file /// @param data data bytes array /// @param size size of data bytes array - extern uint append_bytes(fs::path file, const ubyte* data, size_t size); + uint append_bytes(fs::path file, const ubyte* data, size_t size); /// @brief Write string to the file - extern bool write_string(fs::path filename, const std::string content); + bool write_string(fs::path filename, const std::string content); /// @brief Write dynamic data to the JSON file /// @param nice if true, human readable format will be used, otherwise minimal - extern bool write_json( + bool write_json( fs::path filename, const dynamic::Map* obj, bool nice=true); @@ -52,21 +52,21 @@ namespace files { /// @brief Write dynamic data to the binary JSON file /// (see src/coders/binary_json_spec.md) /// @param compressed use gzip compression - extern bool write_binary_json( + bool write_binary_json( fs::path filename, const dynamic::Map* obj, bool compressed=false ); - extern bool read(fs::path, char* data, size_t size); - extern ubyte* read_bytes(fs::path, size_t& length); - extern std::string read_string(fs::path filename); + bool read(fs::path, char* data, size_t size); + std::unique_ptr read_bytes(fs::path, size_t& length); + std::string read_string(fs::path filename); /// @brief Read JSON or BJSON file /// @param file *.json or *.bjson file - extern std::unique_ptr read_json(fs::path file); - extern std::unique_ptr read_binary_json(fs::path file); - extern std::vector read_list(fs::path file); + std::unique_ptr read_json(fs::path file); + std::unique_ptr read_binary_json(fs::path file); + std::vector read_list(fs::path file); } -#endif /* FILES_FILES_H_ */ \ No newline at end of file +#endif /* FILES_FILES_HPP_ */ diff --git a/src/files/rle.cpp b/src/files/rle.cpp deleted file mode 100644 index 5a5c5714..00000000 --- a/src/files/rle.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "rle.h" - -size_t rle::decode(const ubyte* src, size_t srclen, ubyte* dst) { - size_t offset = 0; - for (size_t i = 0; i < srclen;) { - ubyte len = src[i++]; - ubyte c = src[i++]; - for (size_t j = 0; j <= len; j++) { - dst[offset++] = c; - } - } - return offset; -} - -size_t rle::encode(const ubyte* src, size_t srclen, ubyte* dst) { - if (srclen == 0) { - return 0; - } - size_t offset = 0; - ubyte counter = 0; - ubyte c = src[0]; - for (size_t i = 1; i < srclen; i++) { - ubyte cnext = src[i]; - if (cnext != c || counter == 255) { - dst[offset++] = counter; - dst[offset++] = c; - c = cnext; - counter = 0; - } - else { - counter++; - } - } - dst[offset++] = counter; - dst[offset++] = c; - return offset; -} - - -size_t extrle::decode(const ubyte* src, size_t srclen, ubyte* dst) { - size_t offset = 0; - for (size_t i = 0; i < srclen;) { - uint len = src[i++]; - if (len & 0x80) { - len &= 0x7F; - len |= ((uint)src[i++]) << 7; - } - ubyte c = src[i++]; - for (size_t j = 0; j <= len; j++) { - dst[offset++] = c; - } - } - return offset; -} - -size_t extrle::encode(const ubyte* src, size_t srclen, ubyte* dst) { - if (srclen == 0) { - return 0; - } - size_t offset = 0; - uint counter = 0; - ubyte c = src[0]; - for (size_t i = 1; i < srclen; i++) { - ubyte cnext = src[i]; - if (cnext != c || counter == max_sequence) { - if (counter >= 0x80) { - dst[offset++] = 0x80 | (counter & 0x7F); - dst[offset++] = counter >> 7; - } - else { - dst[offset++] = counter; - } - dst[offset++] = c; - c = cnext; - counter = 0; - } - else { - counter++; - } - } - if (counter >= 0x80) { - dst[offset++] = 0x80 | (counter & 0x7F); - dst[offset++] = counter >> 7; - } - else { - dst[offset++] = counter; - } - dst[offset++] = c; - return offset; -} diff --git a/src/files/rle.h b/src/files/rle.h deleted file mode 100644 index 0dcbe042..00000000 --- a/src/files/rle.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef FILES_RLE_H_ -#define FILES_RLE_H_ - -#include "../typedefs.h" - -namespace rle { - size_t encode(const ubyte* src, size_t length, ubyte* dst); - size_t decode(const ubyte* src, size_t length, ubyte* dst); -} - -namespace extrle { - constexpr uint max_sequence = 0x7FFF; - size_t encode(const ubyte* src, size_t length, ubyte* dst); - size_t decode(const ubyte* src, size_t length, ubyte* dst); -} - -#endif // FILES_RLE_H_ diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index 92184d2d..2769c8fd 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -1,99 +1,173 @@ -#include "settings_io.h" +#include "settings_io.hpp" + +#include "../window/Events.hpp" +#include "../window/input.hpp" +#include "../coders/toml.hpp" +#include "../coders/json.hpp" +#include "../debug/Logger.hpp" +#include "../settings.hpp" #include -#include -#include "../window/Events.h" -#include "../window/input.h" +static debug::Logger logger("settings_io"); -#include "../coders/toml.h" -#include "../coders/json.h" +struct SectionsBuilder { + std::unordered_map& map; + std::vector
& sections; -#include "../data/dynamic.h" - -toml::Wrapper* create_wrapper(EngineSettings& settings) { - std::unique_ptr wrapper (new toml::Wrapper()); - - toml::Section& audio = wrapper->add("audio"); - audio.add("enabled", &settings.audio.enabled); - audio.add("volume-master", &settings.audio.volumeMaster); - audio.add("volume-regular", &settings.audio.volumeRegular); - audio.add("volume-ui", &settings.audio.volumeUI); - audio.add("volume-ambient", &settings.audio.volumeAmbient); - audio.add("volume-music", &settings.audio.volumeMusic); - - toml::Section& display = wrapper->add("display"); - display.add("fullscreen", &settings.display.fullscreen); - display.add("width", &settings.display.width); - display.add("height", &settings.display.height); - display.add("samples", &settings.display.samples); - display.add("swap-interval", &settings.display.swapInterval); - - toml::Section& chunks = wrapper->add("chunks"); - chunks.add("load-distance", &settings.chunks.loadDistance); - chunks.add("load-speed", &settings.chunks.loadSpeed); - chunks.add("padding", &settings.chunks.padding); - - toml::Section& camera = wrapper->add("camera"); - camera.add("fov-effects", &settings.camera.fovEvents); - camera.add("fov", &settings.camera.fov); - camera.add("shaking", &settings.camera.shaking); - camera.add("sensitivity", &settings.camera.sensitivity); - - toml::Section& graphics = wrapper->add("graphics"); - graphics.add("gamma", &settings.graphics.gamma); - graphics.add("fog-curve", &settings.graphics.fogCurve); - graphics.add("backlight", &settings.graphics.backlight); - graphics.add("frustum-culling", &settings.graphics.frustumCulling); - graphics.add("skybox-resolution", &settings.graphics.skyboxResolution); - - toml::Section& debug = wrapper->add("debug"); - debug.add("generator-test-mode", &settings.debug.generatorTestMode); - debug.add("show-chunk-borders", &settings.debug.showChunkBorders); - debug.add("do-write-lights", &settings.debug.doWriteLights); - - toml::Section& ui = wrapper->add("ui"); - ui.add("language", &settings.ui.language); - return wrapper.release(); -} - -std::string write_controls() { - dynamic::Map obj; - for (auto& entry : Events::bindings) { - const auto& binding = entry.second; - - auto& jentry = obj.putMap(entry.first); - switch (binding.type) { - case inputtype::keyboard: jentry.put("type", "keyboard"); break; - case inputtype::mouse: jentry.put("type", "mouse"); break; - default: throw std::runtime_error("unsupported control type"); - } - jentry.put("code", binding.code); + SectionsBuilder( + std::unordered_map& map, + std::vector
& sections + ) : map(map), sections(sections) { } - return json::stringify(&obj, true, " "); + + void section(std::string name) { + sections.push_back(Section {name, {}}); + } + + void add(std::string name, Setting* setting, bool writeable=true) { + Section& section = sections.at(sections.size()-1); + map[section.name+"."+name] = setting; + section.keys.push_back(name); + } +}; + +SettingsHandler::SettingsHandler(EngineSettings& settings) { + SectionsBuilder builder(map, sections); + + builder.section("audio"); + builder.add("enabled", &settings.audio.enabled, false); + builder.add("volume-master", &settings.audio.volumeMaster); + builder.add("volume-regular", &settings.audio.volumeRegular); + builder.add("volume-ui", &settings.audio.volumeUI); + builder.add("volume-ambient", &settings.audio.volumeAmbient); + builder.add("volume-music", &settings.audio.volumeMusic); + + builder.section("display"); + builder.add("width", &settings.display.width); + builder.add("height", &settings.display.height); + builder.add("samples", &settings.display.samples); + builder.add("vsync", &settings.display.vsync); + builder.add("fullscreen", &settings.display.fullscreen); + + builder.section("camera"); + builder.add("sensitivity", &settings.camera.sensitivity); + builder.add("fov", &settings.camera.fov); + builder.add("fov-effects", &settings.camera.fovEffects); + builder.add("shaking", &settings.camera.shaking); + + builder.section("chunks"); + builder.add("load-distance", &settings.chunks.loadDistance); + builder.add("load-speed", &settings.chunks.loadSpeed); + builder.add("padding", &settings.chunks.padding); + + builder.section("graphics"); + builder.add("fog-curve", &settings.graphics.fogCurve); + builder.add("backlight", &settings.graphics.backlight); + builder.add("gamma", &settings.graphics.gamma); + builder.add("frustum-culling", &settings.graphics.frustumCulling); + builder.add("skybox-resolution", &settings.graphics.skyboxResolution); + + builder.section("ui"); + builder.add("language", &settings.ui.language); + builder.add("world-preview-size", &settings.ui.worldPreviewSize); + + builder.section("debug"); + builder.add("generator-test-mode", &settings.debug.generatorTestMode); + builder.add("do-write-lights", &settings.debug.doWriteLights); } -void load_controls(std::string filename, std::string source) { - auto obj = json::parse(filename, source); - for (auto& entry : Events::bindings) { - auto& binding = entry.second; - - auto jentry = obj->map(entry.first); - if (jentry == nullptr) - continue; - inputtype type; - std::string typestr; - jentry->str("type", typestr); - - if (typestr == "keyboard") { - type = inputtype::keyboard; - } else if (typestr == "mouse") { - type = inputtype::mouse; - } else { - std::cerr << "unknown input type '" << typestr << "'" << std::endl; - continue; - } - binding.type = type; - jentry->num("code", binding.code); +std::unique_ptr SettingsHandler::getValue(const std::string& name) const { + auto found = map.find(name); + if (found == map.end()) { + throw std::runtime_error("setting '"+name+"' does not exist"); + } + auto setting = found->second; + if (auto number = dynamic_cast(setting)) { + return dynamic::Value::of((number_t)number->get()); + } else if (auto integer = dynamic_cast(setting)) { + return dynamic::Value::of((integer_t)integer->get()); + } else if (auto flag = dynamic_cast(setting)) { + return dynamic::Value::boolean(flag->get()); + } else if (auto string = dynamic_cast(setting)) { + return dynamic::Value::of(string->get()); + } else { + throw std::runtime_error("type is not implemented for '"+name+"'"); } } + +std::string SettingsHandler::toString(const std::string& name) const { + auto found = map.find(name); + if (found == map.end()) { + throw std::runtime_error("setting '"+name+"' does not exist"); + } + auto setting = found->second; + return setting->toString(); +} + +Setting* SettingsHandler::getSetting(const std::string& name) const { + auto found = map.find(name); + if (found == map.end()) { + throw std::runtime_error("setting '"+name+"' does not exist"); + } + return found->second; +} + +bool SettingsHandler::has(const std::string& name) const { + return map.find(name) != map.end(); +} + +template +static void set_numeric_value(T* setting, const dynamic::Value& value) { + switch (value.type) { + case dynamic::valtype::integer: + setting->set(std::get(value.value)); + break; + case dynamic::valtype::number: + setting->set(std::get(value.value)); + break; + case dynamic::valtype::boolean: + setting->set(std::get(value.value)); + break; + default: + throw std::runtime_error("type error, numeric value expected"); + } +} + +void SettingsHandler::setValue(const std::string& name, const dynamic::Value& value) { + auto found = map.find(name); + if (found == map.end()) { + throw std::runtime_error("setting '"+name+"' does not exist"); + } + auto setting = found->second; + if (auto number = dynamic_cast(setting)) { + set_numeric_value(number, value); + } else if (auto integer = dynamic_cast(setting)) { + set_numeric_value(integer, value); + } else if (auto flag = dynamic_cast(setting)) { + set_numeric_value(flag, value); + } else if (auto string = dynamic_cast(setting)) { + switch (value.type) { + case dynamic::valtype::string: + string->set(std::get(value.value)); + break; + case dynamic::valtype::integer: + string->set(std::to_string(std::get(value.value))); + break; + case dynamic::valtype::number: + string->set(std::to_string(std::get(value.value))); + break; + case dynamic::valtype::boolean: + string->set(std::to_string(std::get(value.value))); + break; + default: + throw std::runtime_error("not implemented for type"); + } + } else { + throw std::runtime_error("type is not implement - setting '"+name+"'"); + } +} + +std::vector
& SettingsHandler::getSections() { + return sections; +} diff --git a/src/files/settings_io.h b/src/files/settings_io.h deleted file mode 100644 index 57a38151..00000000 --- a/src/files/settings_io.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef FILES_SETTINGS_IO_H_ -#define FILES_SETTINGS_IO_H_ - -#include -#include "../settings.h" - -namespace toml { - class Wrapper; -} - -extern std::string write_controls(); -extern toml::Wrapper* create_wrapper(EngineSettings& settings); -extern void load_controls(std::string filename, std::string source); - -#endif // FILES_SETTINGS_IO_H_ \ No newline at end of file diff --git a/src/files/settings_io.hpp b/src/files/settings_io.hpp new file mode 100644 index 00000000..2acfcea4 --- /dev/null +++ b/src/files/settings_io.hpp @@ -0,0 +1,34 @@ +#ifndef FILES_SETTINGS_IO_HPP_ +#define FILES_SETTINGS_IO_HPP_ + +#include "../data/dynamic.hpp" + +#include +#include +#include +#include + +class Setting; +struct EngineSettings; + +struct Section { + std::string name; + std::vector keys; +}; + +class SettingsHandler { + std::unordered_map map; + std::vector
sections; +public: + SettingsHandler(EngineSettings& settings); + + std::unique_ptr getValue(const std::string& name) const; + void setValue(const std::string& name, const dynamic::Value& value); + std::string toString(const std::string& name) const; + Setting* getSetting(const std::string& name) const; + bool has(const std::string& name) const; + + std::vector
& getSections(); +}; + +#endif // FILES_SETTINGS_IO_HPP_ diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index f8e7bd40..373e9730 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -1,15 +1,17 @@ -#include "ContentGfxCache.h" +#include "ContentGfxCache.hpp" + +#include "UiDocument.hpp" + +#include "../assets/Assets.hpp" +#include "../content/Content.hpp" +#include "../content/ContentPack.hpp" +#include "../core_defs.hpp" +#include "../graphics/core/Atlas.hpp" +#include "../maths/UVRegion.hpp" +#include "../voxels/Block.hpp" #include -#include "../assets/Assets.h" -#include "../content/Content.h" -#include "../content/ContentPack.h" -#include "../graphics/Atlas.h" -#include "../voxels/Block.h" -#include "../core_defs.h" -#include "UiDocument.h" - ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : content(content) { auto indices = content->getIndices(); sideregions = std::make_unique(indices->countBlockDefs() * 6); @@ -39,14 +41,6 @@ ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : conte ContentGfxCache::~ContentGfxCache() { } -std::shared_ptr ContentGfxCache::getLayout(const std::string& id) { - auto found = layouts.find(id); - if (found == layouts.end()) { - return nullptr; - } - return found->second; -} - const Content* ContentGfxCache::getContent() const { return content; } diff --git a/src/frontend/ContentGfxCache.h b/src/frontend/ContentGfxCache.h deleted file mode 100644 index b561742b..00000000 --- a/src/frontend/ContentGfxCache.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef FRONTEND_BLOCKS_GFX_CACHE_H_ -#define FRONTEND_BLOCKS_GFX_CACHE_H_ - -#include -#include -#include -#include "../graphics/UVRegion.h" -#include "../typedefs.h" - -class Content; -class Assets; -class UiDocument; - -using uidocuments_map = std::unordered_map>; - -class ContentGfxCache { - const Content* content; - // array of block sides uv regions (6 per block) - std::unique_ptr sideregions; - // all loaded layouts - uidocuments_map layouts; -public: - ContentGfxCache(const Content* content, Assets* assets); - ~ContentGfxCache(); - - inline const UVRegion& getRegion(blockid_t id, int side) const { - return sideregions[id * 6 + side]; - } - - std::shared_ptr getLayout(const std::string& id); - - const Content* getContent() const; -}; - -#endif // FRONTEND_BLOCKS_GFX_CACHE_H_ diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp new file mode 100644 index 00000000..f1f696d4 --- /dev/null +++ b/src/frontend/ContentGfxCache.hpp @@ -0,0 +1,27 @@ +#ifndef FRONTEND_BLOCKS_GFX_CACHE_HPP_ +#define FRONTEND_BLOCKS_GFX_CACHE_HPP_ + +#include "../typedefs.hpp" + +#include + +class Content; +class Assets; +struct UVRegion; + +class ContentGfxCache { + const Content* content; + // array of block sides uv regions (6 per block) + std::unique_ptr sideregions; +public: + ContentGfxCache(const Content* content, Assets* assets); + ~ContentGfxCache(); + + inline const UVRegion& getRegion(blockid_t id, int side) const { + return sideregions[id * 6 + side]; + } + + const Content* getContent() const; +}; + +#endif // FRONTEND_BLOCKS_GFX_CACHE_HPP_ diff --git a/src/frontend/InventoryView.h b/src/frontend/InventoryView.h deleted file mode 100644 index e7a49af1..00000000 --- a/src/frontend/InventoryView.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef FRONTEND_INVENTORY_VIEW_H_ -#define FRONTEND_INVENTORY_VIEW_H_ - -#include -#include -#include - -#include "../frontend/gui/UINode.h" -#include "../frontend/gui/containers.h" -#include "../frontend/gui/controls.h" -#include "../items/ItemStack.h" -#include "../typedefs.h" - -class Assets; -class GfxContext; -class Content; -class ContentIndices; -class LevelFrontend; -class Inventory; - -namespace gui { - class UiXmlReader; -} - -namespace scripting { - class Environment; -} - -using slotcallback = std::function; - -class InventoryInteraction { - ItemStack grabbedItem; -public: - InventoryInteraction() = default; - - ItemStack& getGrabbedItem() { - return grabbedItem; - } -}; - -struct SlotLayout { - int index; - glm::vec2 position; - bool background; - bool itemSource; - slotcallback updateFunc; - slotcallback shareFunc; - slotcallback rightClick; - int padding = 0; - - SlotLayout( - int index, - glm::vec2 position, - bool background, - bool itemSource, - slotcallback updateFunc, - slotcallback shareFunc, - slotcallback rightClick - ); -}; - -class SlotView : public gui::UINode { - LevelFrontend* frontend = nullptr; - InventoryInteraction* interaction = nullptr; - const Content* content; - SlotLayout layout; - bool highlighted = false; - - int64_t inventoryid = 0; - ItemStack* bound = nullptr; -public: - SlotView(SlotLayout layout); - - virtual void draw(const GfxContext* pctx, Assets* assets) override; - - void setHighlighted(bool flag); - bool isHighlighted() const; - - virtual void clicked(gui::GUI*, mousecode) override; - virtual void onFocus(gui::GUI*) override; - - void bind( - int64_t inventoryid, - ItemStack& stack, - LevelFrontend* frontend, - InventoryInteraction* interaction - ); - - const SlotLayout& getLayout() const; -}; - -class InventoryView : public gui::Container { - const Content* content; - const ContentIndices* indices; - - std::shared_ptr inventory; - LevelFrontend* frontend = nullptr; - InventoryInteraction* interaction = nullptr; - - std::vector slots; - glm::vec2 origin {}; -public: - InventoryView(); - virtual ~InventoryView(); - - void setInventory(std::shared_ptr inventory); - - virtual void setPos(glm::vec2 pos) override; - - void setOrigin(glm::vec2 origin); - glm::vec2 getOrigin() const; - - void setSelected(int index); - - void bind( - std::shared_ptr inventory, - LevelFrontend* frontend, - InventoryInteraction* interaction - ); - - void unbind(); - - std::shared_ptr addSlot(SlotLayout layout); - - std::shared_ptr getInventory() const; - - size_t getSlotsCount() const; - - static void createReaders(gui::UiXmlReader& reader); - - static const int SLOT_INTERVAL = 4; - static const int SLOT_SIZE = ITEM_ICON_SIZE; -}; - -class InventoryBuilder { - std::shared_ptr view; -public: - InventoryBuilder(); - - /// @brief Add slots grid to inventory view - /// @param cols grid columns - /// @param count total number of grid slots - /// @param pos position of the first slot of the grid - /// @param padding additional space around the grid - /// @param addpanel automatically create panel behind the grid - /// with size including padding - /// @param slotLayout slot settings (index and position are ignored) - void addGrid( - int cols, int count, - glm::vec2 pos, - int padding, - bool addpanel, - SlotLayout slotLayout - ); - - void add(SlotLayout slotLayout); - std::shared_ptr build(); -}; - -#endif // FRONTEND_INVENTORY_VIEW_H_ diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 50267ee4..d44df1d1 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -1,25 +1,27 @@ -#include "LevelFrontend.h" +#include "LevelFrontend.hpp" -#include "BlocksPreview.h" -#include "ContentGfxCache.h" +#include "ContentGfxCache.hpp" -#include "../audio/audio.h" -#include "../world/Level.h" -#include "../voxels/Block.h" -#include "../assets/Assets.h" -#include "../graphics/Atlas.h" -#include "../content/Content.h" - -#include "../logic/LevelController.h" -#include "../logic/PlayerController.h" +#include "../assets/Assets.hpp" +#include "../audio/audio.hpp" +#include "../content/Content.hpp" +#include "../graphics/core/Atlas.hpp" +#include "../graphics/render/BlocksPreview.hpp" +#include "../logic/LevelController.hpp" +#include "../logic/PlayerController.hpp" +#include "../voxels/Block.hpp" +#include "../world/Level.hpp" LevelFrontend::LevelFrontend(LevelController* controller, Assets* assets) : level(controller->getLevel()), controller(controller), assets(assets), - contentCache(std::make_unique(level->content, assets)), - blocksAtlas(BlocksPreview::build(contentCache.get(), assets, level->content)) + contentCache(std::make_unique(level->content, assets)) { + assets->store( + BlocksPreview::build(contentCache.get(), assets, level->content).release(), + "block-previews" + ); controller->getPlayerController()->listenBlockInteraction( [=](Player*, glm::ivec3 pos, const Block* def, BlockInteraction type) { auto material = level->content->findBlockMaterial(def->material); @@ -81,10 +83,6 @@ ContentGfxCache* LevelFrontend::getContentGfxCache() const { return contentCache.get(); } -Atlas* LevelFrontend::getBlocksAtlas() const { - return blocksAtlas.get(); -} - LevelController* LevelFrontend::getController() const { return controller; } diff --git a/src/frontend/LevelFrontend.h b/src/frontend/LevelFrontend.hpp similarity index 69% rename from src/frontend/LevelFrontend.h rename to src/frontend/LevelFrontend.hpp index f31ac7e0..6e100670 100644 --- a/src/frontend/LevelFrontend.h +++ b/src/frontend/LevelFrontend.hpp @@ -1,12 +1,10 @@ -#ifndef FRONTEND_LEVEL_FRONTEND_H_ -#define FRONTEND_LEVEL_FRONTEND_H_ +#ifndef FRONTEND_LEVEL_FRONTEND_HPP_ +#define FRONTEND_LEVEL_FRONTEND_HPP_ #include -class Atlas; class Level; class Assets; -class BlocksPreview; class ContentGfxCache; class LevelController; @@ -15,7 +13,6 @@ class LevelFrontend { LevelController* controller; Assets* assets; std::unique_ptr contentCache; - std::unique_ptr blocksAtlas; public: LevelFrontend(LevelController* controller, Assets* assets); ~LevelFrontend(); @@ -23,9 +20,7 @@ public: Level* getLevel() const; Assets* getAssets() const; ContentGfxCache* getContentGfxCache() const; - Atlas* getBlocksAtlas() const; - LevelController* getController() const; }; -#endif // FRONTEND_LEVEL_FRONTEND_H_ +#endif // FRONTEND_LEVEL_FRONTEND_HPP_ diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 0690eaa9..5cf09589 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -1,27 +1,32 @@ -#include "UiDocument.h" +#include "UiDocument.hpp" -#include -#include "gui/UINode.h" -#include "gui/containers.h" -#include "InventoryView.h" -#include "../logic/scripting/scripting.h" -#include "../files/files.h" -#include "../frontend/gui/gui_xml.h" +#include "../files/files.hpp" +#include "../graphics/ui/elements/UINode.hpp" +#include "../graphics/ui/elements/InventoryView.hpp" +#include "../graphics/ui/gui_xml.hpp" +#include "../logic/scripting/scripting.hpp" UiDocument::UiDocument( std::string id, uidocscript script, std::shared_ptr root, - std::unique_ptr env -) : id(id), script(script), root(root), env(std::move(env)) { - collect(map, root); + scriptenv env +) : id(id), script(script), root(root), env(env) { + gui::UINode::getIndices(root, map); } +void UiDocument::rebuildIndices() { + gui::UINode::getIndices(root, map); +} const uinodes_map& UiDocument::getMap() const { return map; } +uinodes_map& UiDocument::getMapWriteable() { + return map; +} + const std::string& UiDocument::getId() const { return id; } @@ -42,30 +47,19 @@ const uidocscript& UiDocument::getScript() const { return script; } -int UiDocument::getEnvironment() const { - return env->getId(); +scriptenv UiDocument::getEnvironment() const { + return env; } -void UiDocument::collect(uinodes_map& map, std::shared_ptr node) { - const std::string& id = node->getId(); - if (!id.empty()) { - map[id] = node; - } - auto container = dynamic_cast(node.get()); - if (container) { - for (auto subnode : container->getNodes()) { - collect(map, subnode); - } - } -} - -std::unique_ptr UiDocument::read(AssetsLoader& loader, int penv, std::string namesp, fs::path file) { +std::unique_ptr UiDocument::read(scriptenv penv, std::string name, fs::path file) { const std::string text = files::read_string(file); auto xmldoc = xml::parse(file.u8string(), text); - auto env = scripting::create_doc_environment(penv, namesp); - gui::UiXmlReader reader(*env, loader); - InventoryView::createReaders(reader); + auto env = penv == nullptr + ? scripting::get_root_environment() + : scripting::create_doc_environment(penv, name); + + gui::UiXmlReader reader(env); auto view = reader.readXML( file.u8string(), xmldoc->getRoot() ); @@ -73,7 +67,12 @@ std::unique_ptr UiDocument::read(AssetsLoader& loader, int penv, std uidocscript script {}; auto scriptFile = fs::path(file.u8string()+".lua"); if (fs::is_regular_file(scriptFile)) { - scripting::load_layout_script(env->getId(), namesp, scriptFile, script); + scripting::load_layout_script(env, name, scriptFile, script); } - return std::make_unique(namesp, script, view, std::move(env)); + return std::make_unique(name, script, view, env); +} + +std::shared_ptr UiDocument::readElement(fs::path file) { + auto document = read(nullptr, file.filename().u8string(), file); + return document->getRoot(); } diff --git a/src/frontend/UiDocument.h b/src/frontend/UiDocument.hpp similarity index 56% rename from src/frontend/UiDocument.h rename to src/frontend/UiDocument.hpp index 8dad5cb0..d45b8783 100644 --- a/src/frontend/UiDocument.h +++ b/src/frontend/UiDocument.hpp @@ -1,5 +1,7 @@ -#ifndef FRONTEND_UI_DOCUMENT_H_ -#define FRONTEND_UI_DOCUMENT_H_ +#ifndef FRONTEND_UI_DOCUMENT_HPP_ +#define FRONTEND_UI_DOCUMENT_HPP_ + +#include "../typedefs.hpp" #include #include @@ -12,44 +14,40 @@ namespace gui { class UINode; } -namespace scripting { - class Environment; -} - struct uidocscript { - int environment; bool onopen : 1; + bool onprogress : 1; bool onclose : 1; }; using uinodes_map = std::unordered_map>; -class AssetsLoader; - class UiDocument { std::string id; uidocscript script; uinodes_map map; std::shared_ptr root; - std::unique_ptr env; + scriptenv env; public: UiDocument( std::string id, uidocscript script, std::shared_ptr root, - std::unique_ptr env + scriptenv env ); + void rebuildIndices(); + const std::string& getId() const; const uinodes_map& getMap() const; + uinodes_map& getMapWriteable(); const std::shared_ptr getRoot() const; const std::shared_ptr get(const std::string& id) const; const uidocscript& getScript() const; - int getEnvironment() const; - /* Collect map of all uinodes having identifiers */ - static void collect(uinodes_map& map, std::shared_ptr node); + scriptenv getEnvironment() const; - static std::unique_ptr read(AssetsLoader& loader, int env, std::string namesp, fs::path file); + static std::unique_ptr read(scriptenv parent_env, std::string name, fs::path file); + static std::shared_ptr readElement(fs::path file); }; -#endif // FRONTEND_UI_DOCUMENT_H_ +#endif // FRONTEND_UI_DOCUMENT_HPP_ diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 1af94bb0..a967ccad 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -1,22 +1,25 @@ +#include "../audio/audio.hpp" +#include "../delegates.hpp" +#include "../engine.hpp" +#include "../graphics/core/Mesh.hpp" +#include "../graphics/ui/elements/CheckBox.hpp" +#include "../graphics/ui/elements/TextBox.hpp" +#include "../graphics/ui/elements/TrackBar.hpp" +#include "../graphics/ui/elements/InputBindBox.hpp" +#include "../graphics/render/WorldRenderer.hpp" +#include "../objects/Player.hpp" +#include "../physics/Hitbox.hpp" +#include "../util/stringutil.hpp" +#include "../voxels/Block.hpp" +#include "../voxels/Chunk.hpp" +#include "../voxels/Chunks.hpp" +#include "../world/Level.hpp" +#include "../world/World.hpp" + #include #include #include -#include "gui/controls.h" -#include "../audio/audio.h" -#include "../graphics/Mesh.h" -#include "../objects/Player.h" -#include "../physics/Hitbox.h" -#include "../world/Level.h" -#include "../world/World.h" -#include "../voxels/Chunks.h" -#include "../voxels/Block.h" -#include "../util/stringutil.h" -#include "../delegates.h" -#include "../engine.h" - -#include "WorldRenderer.h" - using namespace gui; static std::shared_ptr