merge main
3
.gitignore
vendored
@ -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
|
||||
!/res/content/base
|
||||
|
||||
@ -101,20 +101,19 @@ if (WIN32)
|
||||
else()
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LUAJIT REQUIRED luajit)
|
||||
pkg_check_modules(VORBIS REQUIRED vorbis vorbisfile)
|
||||
set(LUA_LIBRARIES ${LUAJIT_LIBRARIES})
|
||||
set(LUA_INCLUDE_DIR ${LUAJIT_INCLUDE_DIR})
|
||||
set(LUA_INCLUDE_DIR ${LUAJIT_INCLUDE_DIRS})
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(Lua REQUIRED)
|
||||
set(PNGLIB PNG::PNG)
|
||||
set(VORBISLIB vorbis vorbisfile)
|
||||
set(VORBISLIB ${VORBIS_LDFLAGS})
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
find_package(glfw3 3.3 REQUIRED)
|
||||
endif ()
|
||||
|
||||
set(LIBS "")
|
||||
|
||||
if(UNIX)
|
||||
find_package(glfw3 3.3 REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
set(LIBS ${LIBS} Threads::Threads)
|
||||
endif()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Run with compiled executable file:
|
||||
|
||||
[Download | Скачать](https://github.com/MihailRis/VoxelEngine-Cpp/releases/tag/v20.0)
|
||||
[Download | Скачать](https://github.com/MihailRis/VoxelEngine-Cpp/releases/latest)
|
||||
|
||||
|
||||
# Controls:
|
||||
@ -42,7 +42,7 @@ sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||
|
||||
#### RHEL-based distro:
|
||||
```sh
|
||||
sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel libvorbis-devel openal-devel luajit
|
||||
sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel libvorbis-devel openal-devel luajit-devel
|
||||
```
|
||||
|
||||
#### Arch-based distro:
|
||||
|
||||
@ -26,9 +26,7 @@ AppDir:
|
||||
- libglew2.1
|
||||
- libpng16-16
|
||||
- libopenal1
|
||||
- libopengl0
|
||||
- libasound2
|
||||
- libglx0
|
||||
- libogg0
|
||||
- libvorbis0a
|
||||
- libvorbisfile3
|
||||
|
||||
@ -13,6 +13,30 @@ load_script("packid:scripts/script_name.lua") -- load Lua script if not loaded y
|
||||
load_script("packid:scripts/script_name.lua", true) -- load Lua script anyway
|
||||
```
|
||||
|
||||
## *pack* library
|
||||
|
||||
```python
|
||||
pack.is_installed(packid: str) -> bool
|
||||
```
|
||||
|
||||
Check if specified pack is installed in the world
|
||||
|
||||
```python
|
||||
pack.data_file(packid: str, filename: str) -> str
|
||||
```
|
||||
|
||||
Returns data file path like `world:data/packid/filename`
|
||||
and creates missing directories.
|
||||
|
||||
Use this function when saving pack settings or other data to the world.
|
||||
|
||||
Example:
|
||||
```lua
|
||||
file.write(pack.data_file(PACK_ID, "example.txt"), text)
|
||||
```
|
||||
|
||||
For pack *containermod* will write text to the file `world:data/containermod/example.txt`
|
||||
|
||||
## *player* library
|
||||
|
||||
|
||||
@ -71,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**)
|
||||
@ -137,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
|
||||
@ -313,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
|
||||
|
||||
@ -60,10 +60,6 @@
|
||||
|
||||
- `src` - имя изображения в папке textures без указания расширения. Тип: строка. Например `gui/error`
|
||||
|
||||
## Изображение `image`
|
||||
|
||||
- `src` - имя изображения в папке textures без указания расширения. Тип: строка. Например `gui/error`
|
||||
|
||||
# Текстовое поле `textbox`
|
||||
|
||||
Внутренний текст - изначально введенный текст
|
||||
@ -110,4 +106,4 @@
|
||||
- `padding` - отступ вокруг решетки слотов. Тип: число. (*атрибут будет удален*)
|
||||
- `sharefunc` - lua событие вызываемое при использовании ЛКМ + Shift. Передается id инвентаря и индекс слота
|
||||
- `updatefunc` - lua событие вызываемое при изменении содержимого слота
|
||||
- `onrightclick` - lua событие вызываемое при использовании ПКМ. Передается id инвентаря и индекс слота
|
||||
- `onrightclick` - lua событие вызываемое при использовании ПКМ. Передается id инвентаря и индекс слота
|
||||
|
||||
@ -10,6 +10,29 @@ load_script("контентпак:scripts/имя_скрипта.lua", true) --
|
||||
require "контентпак:имя_модуля" -- загружает lua модуль из папки modules (расширение не указывается)
|
||||
```
|
||||
|
||||
## Библиотека pack
|
||||
|
||||
```python
|
||||
pack.is_installed(packid: str) -> bool
|
||||
```
|
||||
|
||||
Проверяет наличие установленного пака в мире
|
||||
|
||||
```python
|
||||
pack.data_file(packid: str, filename: str) -> str
|
||||
```
|
||||
|
||||
Возвращает путь к файлу данных по типу: `world:data/packid/filename`
|
||||
и создает недостающие директории в пути.
|
||||
|
||||
Используйте эту функцию при сохранении настроек пака или иных данных в мире.
|
||||
|
||||
Пример:
|
||||
```lua
|
||||
file.write(pack.data_file(PACK_ID, "example.txt"), text)
|
||||
```
|
||||
Для пака *containermod* запишет текст в файл `world:data/containermod/example.txt`
|
||||
|
||||
## Библиотека player
|
||||
```python
|
||||
player.get_pos(playerid: int) -> number, number, number
|
||||
@ -66,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:
|
||||
@ -132,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
|
||||
@ -303,6 +354,13 @@ hud.close(layoutid: str)
|
||||
```
|
||||
|
||||
Удаляет элемент с экрана
|
||||
|
||||
```python
|
||||
hud.get_block_inventory() -> int
|
||||
```
|
||||
|
||||
Получить ID инвентаря открытого блока или 0
|
||||
|
||||
## События блоков
|
||||
|
||||
```lua
|
||||
|
||||
@ -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
|
||||
@ -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=Бревно
|
||||
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=бревно
|
||||
|
||||
22
res/layouts/console.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<container color='#00000080' size='400' size-func="unpack(gui.get_viewport())">
|
||||
<container size-func="gui.get_viewport()[1],gui.get_viewport()[2]-40">
|
||||
<textbox
|
||||
id='log'
|
||||
color='0'
|
||||
autoresize='true'
|
||||
margin='0'
|
||||
editable='false'
|
||||
multiline='true'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
gravity="bottom-left"
|
||||
></textbox>
|
||||
</container>
|
||||
<textbox id='prompt'
|
||||
consumer='submit'
|
||||
margin='0'
|
||||
gravity='bottom-left'
|
||||
size-func="gui.get_viewport()[1],40"
|
||||
onup="on_history_up()"
|
||||
ondown="on_history_down()">
|
||||
</textbox>
|
||||
</container>
|
||||
53
res/layouts/console.xml.lua
Normal file
@ -0,0 +1,53 @@
|
||||
history = session.get_entry("commands_history")
|
||||
history_pointer = #history
|
||||
|
||||
function setup_variables()
|
||||
local x,y,z = player.get_pos(hud.get_player())
|
||||
console.set('pos.x', x)
|
||||
console.set('pos.y', y)
|
||||
console.set('pos.z', z)
|
||||
end
|
||||
|
||||
function on_history_up()
|
||||
if history_pointer == 0 then
|
||||
return
|
||||
end
|
||||
document.prompt.text = history[history_pointer]
|
||||
document.prompt.caret = -1
|
||||
history_pointer = history_pointer - 1
|
||||
end
|
||||
|
||||
function on_history_down()
|
||||
if history_pointer == #history-1 then
|
||||
return
|
||||
end
|
||||
history_pointer = history_pointer + 1
|
||||
document.prompt.text = history[history_pointer + 1]
|
||||
document.prompt.caret = -1
|
||||
end
|
||||
|
||||
function add_to_history(text)
|
||||
table.insert(history, text)
|
||||
history_pointer = #history
|
||||
end
|
||||
|
||||
function submit(text)
|
||||
add_to_history(text)
|
||||
setup_variables()
|
||||
|
||||
local status, result = pcall(function() return console.execute(text) end)
|
||||
if result ~= nil then
|
||||
local prevtext = document.log.text
|
||||
if #prevtext == 0 then
|
||||
document.log:paste(tostring(result))
|
||||
else
|
||||
document.log:paste('\n'..tostring(result))
|
||||
end
|
||||
end
|
||||
document.prompt.text = ""
|
||||
document.prompt.focused = true
|
||||
end
|
||||
|
||||
function on_open()
|
||||
document.prompt.focused = true
|
||||
end
|
||||
@ -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
|
||||
|
||||
4
res/layouts/pages/404.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<panel size='400' padding='8' interval='8' color='0'>
|
||||
<label context='menu'>@Page not found</label>
|
||||
<button onclick='menu:back()'>@Back</button>
|
||||
</panel>
|
||||
13
res/layouts/pages/content.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<container size='887,454' color='#0F1E2DB2' padding='8' interval='5' context='menu'>
|
||||
<panel id='packs_cur' pos='2' size='440,406' color='0' max-length='406'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<panel id='packs_add' pos='445,2' size='440,406' color='0' max-length='406'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<button id='apply_btn' pos='2,410' size='440,40' onclick='apply()'>@Apply</button>
|
||||
<button pos='445,410' size='398,40' onclick='menu:back()'>@Cancel</button>
|
||||
<image onclick='refresh()' interactive='true' src='gui/refresh'
|
||||
size='32' margin='7' gravity='bottom-right'
|
||||
color='#FFFFFF80' hover-color='#FFFFFF10'/>
|
||||
</container>
|
||||
131
res/layouts/pages/content.xml.lua
Normal file
@ -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
|
||||
3
res/layouts/pages/generators.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<panel size='400' color='#00000080' padding='8' context='settings'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
16
res/layouts/pages/generators.xml.lua
Normal file
@ -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("<button onclick='menu:back()'>@Back</button>")
|
||||
end
|
||||
3
res/layouts/pages/languages.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<panel size='400' color='#00000080' padding='8'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
19
res/layouts/pages/languages.xml.lua
Normal file
@ -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(
|
||||
"<button onclick=%q>%s</button>",
|
||||
string.format("core.set_setting('ui.language', %q) menu:back()", invlocales[k]), k
|
||||
))
|
||||
end
|
||||
panel:add("<button onclick='menu:back()'>@Back</button>")
|
||||
end
|
||||
7
res/layouts/pages/main.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<panel size='400' color='0' interval='1' context='menu'>
|
||||
<button onclick='menu.page="new_world"'>@New World</button>
|
||||
<panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'>
|
||||
</panel>
|
||||
<button onclick='menu.page="settings"'>@Settings</button>
|
||||
<button onclick='core.quit()'>@Quit</button>
|
||||
</panel>
|
||||
6
res/layouts/pages/main.xml.lua
Normal file
@ -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
|
||||
18
res/layouts/pages/new_world.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<panel size='400' color='0' interval='1' context='world'>
|
||||
<label>@Name</label>
|
||||
<textbox id='name_box'
|
||||
validator='world_name_validator'
|
||||
placeholder='New World'
|
||||
padding='4'>
|
||||
</textbox>
|
||||
<label>@Seed</label>
|
||||
<textbox id='seed_box' placeholder='-' padding='4'></textbox>
|
||||
<button onclick='save_state() menu.page="generators"' id='generator_btn'>
|
||||
@World generator
|
||||
</button>
|
||||
<button id='content_btn' onclick='save_state() menu.page="content"'/>
|
||||
<button onclick='create_world()' margin='1,20,1,1'>
|
||||
@Create World
|
||||
</button>
|
||||
<button onclick='menu:back()'>@Back</button>
|
||||
</panel>
|
||||
47
res/layouts/pages/new_world.xml.lua
Normal file
@ -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
|
||||
8
res/layouts/pages/pause.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<panel size='400' color='0' interval='1' context='menu'>
|
||||
<button onclick='menu:reset()'>@Continue</button>
|
||||
<button onclick='menu.page="content?mode=world"'>@Content</button>
|
||||
<button onclick='menu.page="settings"'>@Settings</button>
|
||||
<button onclick='core.close_world(true)'>
|
||||
@Save and Quit to Menu
|
||||
</button>
|
||||
</panel>
|
||||
14
res/layouts/pages/settings.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<container size='668,418' color='#0F1E2DB2' context='menu'>
|
||||
<panel pos='6' size='250' color='0' interval='1'>
|
||||
<button id='s_aud' onclick='set_page("s_aud", "settings_audio")'>@Audio</button>
|
||||
<button id='s_gfx' onclick='set_page("s_gfx", "settings_graphics")'>@Graphics</button>
|
||||
<button id='s_ctl' onclick='set_page("s_ctl", "settings_controls")'>@Controls</button>
|
||||
</panel>
|
||||
<pagebox id='menu' pos='260,6' size='400'>
|
||||
</pagebox>
|
||||
|
||||
<panel margin='6' gravity='bottom-left' size='250' color='0' interval='1'>
|
||||
<button onclick='menu.page="languages"' id='langs_btn'>-</button>
|
||||
<button onclick='menu:back()'>@Back</button>
|
||||
</panel>
|
||||
</container>
|
||||
15
res/layouts/pages/settings.xml.lua
Normal file
@ -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
|
||||
2
res/layouts/pages/settings_audio.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<panel size='400' color='0' interval='1'>
|
||||
</panel>
|
||||
33
res/layouts/pages/settings_audio.xml.lua
Normal file
@ -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
|
||||
10
res/layouts/pages/settings_controls.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<panel size='400' color='0'>
|
||||
<label id='sensitivity_label'>-</label>
|
||||
<trackbar id='sensitivity_track'
|
||||
min='0.1' max='10' value='2' step='0.1'
|
||||
consumer='change_sensitivity'>
|
||||
</trackbar>
|
||||
<panel id='bindings_panel' size='380,204' padding='2' interval='1' max-length='350' color='#0000004C'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
</panel>
|
||||
25
res/layouts/pages/settings_controls.xml.lua
Normal file
@ -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
|
||||
2
res/layouts/pages/settings_graphics.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<panel size='400' color='0' interval='1' context='menu'>
|
||||
</panel>
|
||||
44
res/layouts/pages/settings_graphics.xml.lua
Normal file
@ -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(
|
||||
"<checkbox consumer='function(x) core.set_setting(\"%s\", x) end' checked='%s'>%s</checkbox>",
|
||||
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
|
||||
4
res/layouts/process.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<panel size='400' padding='8' interval='1' color='#00000080'>
|
||||
<label id='title_label'>???</label>
|
||||
<label id='progress_label'>-</label>
|
||||
</panel>
|
||||
10
res/layouts/process.xml.lua
Normal file
@ -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
|
||||
7
res/layouts/reports/missing_content.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<panel size='500' color='#00000080' padding='8' context='menu'>
|
||||
<label>@missing-content</label>
|
||||
<panel id='content_panel' size='480,100' color='#00000080' scrollable='true' max-length='400'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<button onclick='menu:back()'>@Back to Main Menu</button>
|
||||
</panel>
|
||||
5
res/layouts/reports/missing_content.xml.lua
Normal file
@ -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
|
||||
4
res/layouts/templates/binding.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<panel size='400,40' padding='4' color='0' orientation='horizontal'>
|
||||
<bindbox binding='%{id}'/>
|
||||
<label margin='6'>%{name}</label>
|
||||
</panel>
|
||||
4
res/layouts/templates/content_entry.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<panel size='500,20' color='0' orientation='horizontal' padding='2'>
|
||||
<label color='#80808080'>[%{type}]</label>
|
||||
<label color='#FF333380'>"%{name}</label>
|
||||
</panel>
|
||||
4
res/layouts/templates/generator.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<container onclick='%{callback}' size='80,30' hover-color='#FFFFFF10'>
|
||||
<label pos='80,4' size='300,25' color='#FFFFFF80' align='right'>[%{id}]</label>
|
||||
<label pos='0,8'>%{name}</label>
|
||||
</container>
|
||||
15
res/layouts/templates/pack.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' z-index="%{index}">
|
||||
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose}]
|
||||
</label>
|
||||
<label pos='78,6'>%{title}</label>
|
||||
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'
|
||||
gravity='bottom-right'>
|
||||
%{creator}
|
||||
</label>
|
||||
<label if='%{error}' pos='80,28' size='355,50' multiline='true' color='#FF4040'>%{error}</label>
|
||||
<label ifnot='%{error}' pos='80,28' size='355,50' multiline='true' color='#FFFFFFB2'>
|
||||
%{description}
|
||||
</label>
|
||||
<image pos='8,8' src='%{icon}' size='64'/>
|
||||
</container>
|
||||
6
res/layouts/templates/track_setting.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<panel color='0' context='settings'>
|
||||
<label id='%{id}.L' margin='0,3,0,0'>%{name}: %{value}%{postfix}</label>
|
||||
<trackbar
|
||||
value='%{value}' min='%{min}' max='%{max}' step='%{step}'
|
||||
consumer='function(x) update_setting(x, "%{id}", "%{name}", "%{postfix}") end'/>
|
||||
</panel>
|
||||
17
res/layouts/templates/world.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<container
|
||||
size='380,66'
|
||||
color='#0F1E2DB2'
|
||||
hover-color='#162B3399'
|
||||
onclick='core.open_world("%{name}")'
|
||||
>
|
||||
<image pos='1' src='%{icon}' size='96,64'></image>
|
||||
<label pos='104,8'>%{name}</label>
|
||||
<button margin='4'
|
||||
gravity='center-right'
|
||||
color='#00000000'
|
||||
hover-color='#FFFFFF2B'
|
||||
padding='0'
|
||||
onclick='core.delete_world("%{name}")'>
|
||||
<image src='gui/delete_icon' size='32,32' color='#FFFFFF50'/>
|
||||
</button>
|
||||
</container>
|
||||
@ -11,4 +11,3 @@
|
||||
"gui/crosshair"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
89
res/scripts/stdcmd.lua
Normal file
@ -0,0 +1,89 @@
|
||||
local SEPARATOR = "________________"
|
||||
SEPARATOR = SEPARATOR..SEPARATOR..SEPARATOR
|
||||
|
||||
function build_scheme(command)
|
||||
local str = command.name.." "
|
||||
for i,arg in ipairs(command.args) do
|
||||
if arg.optional then
|
||||
str = str.."["..arg.name.."] "
|
||||
else
|
||||
str = str.."<"..arg.name.."> "
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
console.add_command(
|
||||
"help name:str=''",
|
||||
"Show help infomation for the specified command",
|
||||
function (args, kwargs)
|
||||
local name = args[1]
|
||||
if #name == 0 then
|
||||
local commands = console.get_commands_list()
|
||||
table.sort(commands)
|
||||
local str = "Available commands:"
|
||||
for i,k in ipairs(commands) do
|
||||
str = str.."\n "..build_scheme(console.get_command_info(k))
|
||||
end
|
||||
return str.."\nuse 'help <command>'"
|
||||
end
|
||||
local command = console.get_command_info(name)
|
||||
if command == nil then
|
||||
return string.format("command %q not found", name)
|
||||
end
|
||||
local where = ""
|
||||
local str = SEPARATOR.."\n"..command.description.."\n"..name.." "
|
||||
for i,arg in ipairs(command.args) do
|
||||
where = where.."\n "..arg.name.." - "..arg.type
|
||||
if arg.optional then
|
||||
str = str.."["..arg.name.."] "
|
||||
where = where.." (optional)"
|
||||
else
|
||||
str = str.."<"..arg.name.."> "
|
||||
end
|
||||
end
|
||||
if #command.args then
|
||||
str = str.."\nwhere"..where
|
||||
end
|
||||
|
||||
return str.."\n"..SEPARATOR
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"obj.tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z",
|
||||
"Teleport object",
|
||||
function (args, kwargs)
|
||||
player.set_pos(unpack(args))
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"echo value:str",
|
||||
"Print value to the console",
|
||||
function (args, kwargs)
|
||||
return args[1]
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"time.set value:num",
|
||||
"Set day time [0..1] where 0 is midnight, 0.5 is noon",
|
||||
function (args, kwargs)
|
||||
return world.set_day_time(args[1])
|
||||
end
|
||||
)
|
||||
console.add_command(
|
||||
"blocks.fill id:str x:num~pos.x y:num~pos.y z:num~pos.z w:int h:int d:int",
|
||||
"Fill specified zone with blocks",
|
||||
function (args, kwargs)
|
||||
local name, x, y, z, w, h, d = unpack(args)
|
||||
local id = block.index(name)
|
||||
for ly=0,h-1 do
|
||||
for lz=0,d-1 do
|
||||
for lx=0,w-1 do
|
||||
block.set(x+lx, y+ly, z+lz, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tostring(w*h*d).." blocks set"
|
||||
end
|
||||
)
|
||||
@ -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,114 @@ 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
|
||||
|
||||
function file.readlines(path)
|
||||
local str = file.read(path)
|
||||
local lines = {}
|
||||
for s in str:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, s)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Deprecated functions
|
||||
block_index = block.index
|
||||
block_name = block.name
|
||||
|
||||
70
res/texts/de_DE.txt
Normal file
@ -0,0 +1,70 @@
|
||||
# Общее
|
||||
Yes=Ja
|
||||
No=Nein
|
||||
Ok=OK
|
||||
Cancel=Stornieren
|
||||
Back=Zurück
|
||||
Continue=Weitermachen
|
||||
Add=Hinzufügen
|
||||
Converting world...=Weltkonvertierung im Gange...
|
||||
|
||||
error.pack-not-found=Paket konnte nicht gefunden werden
|
||||
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.Save and Quit to Menu=Speichern und zum Menü zurückkehren
|
||||
menu.Settings=Einstellungen
|
||||
|
||||
world.Seed=Mais
|
||||
world.Name=Name
|
||||
world.World generator=Weltgenerator
|
||||
world.generators.default=Normal
|
||||
world.generators.flat=Wohnung
|
||||
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.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.V-Sync=Vertikale Synchronisation
|
||||
|
||||
# Управление
|
||||
movement.forward=Vorwärts
|
||||
movement.back=Zurück
|
||||
movement.left=Links
|
||||
movement.right=Rechts
|
||||
movement.jump=Prallen
|
||||
movement.sprint=Beschleunigung
|
||||
movement.crouch=Stehlen
|
||||
movement.cheat=Schummeln
|
||||
hud.inventory=Inventar
|
||||
player.pick=Wählen Sie Blockieren
|
||||
player.attack=Angriff / Pause
|
||||
player.build=Setzen Sie einen Block
|
||||
player.flight=Flug
|
||||
camera.zoom=Annäherung
|
||||
camera.mode=Kameramodus ändern
|
||||
@ -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?
|
||||
@ -9,6 +9,7 @@ world.generators.default=Default
|
||||
world.generators.flat=Flat
|
||||
|
||||
# Bindings
|
||||
devtools.console=Console
|
||||
movement.forward=Forward
|
||||
movement.back=Back
|
||||
movement.left=Left
|
||||
@ -24,4 +25,4 @@ player.build=Place Block
|
||||
player.flight=Flight
|
||||
player.noclip=No-clip
|
||||
camera.zoom=Zoom
|
||||
camera.mode=Switch Camera Mode
|
||||
camera.mode=Switch Camera Mode
|
||||
|
||||
@ -1,33 +1,39 @@
|
||||
# Общее
|
||||
Yes=Joo
|
||||
No=Ei
|
||||
Ok = Kyllä
|
||||
Cancel= Hylää
|
||||
Back = Takaisin
|
||||
Ok=Ok
|
||||
Cancel=Hylää
|
||||
Back=Takaisin
|
||||
Continue=Jatka
|
||||
Add=lisätä
|
||||
Converting world...=Maailman muuttuminen on käynnissä...
|
||||
|
||||
error.pack-not-found=Pakettia ei löytynyt!
|
||||
error.dependency-not-found=Käytetty riippuvuutta ei löytynyt
|
||||
pack.remove-confirm=Poistetaanko kaikki pakettien sisältö maailmasta (peruuttamattomasti)?
|
||||
|
||||
# Меню
|
||||
menu.New World = Uusi Maailma
|
||||
menu.New World=Uusi Maailma
|
||||
menu.Quit=Poistu
|
||||
menu.Continue=Jatka
|
||||
menu.Save and Quit to Menu=Tallenna ja poistu valikkoon
|
||||
menu.missing-content=Puuttuu lisäosia!
|
||||
menu.Content Error=Lisäosa virhe!
|
||||
menu.missing-content=Ei ole sisältöä!
|
||||
menu.Content Error=Sisältövirhe
|
||||
menu.Controls=Ohjaus
|
||||
menu.Back to Main Menu=Takaisin Valikoon
|
||||
menu.Settings=Asetukset
|
||||
menu.Content=Lisäosat
|
||||
menu.Seed=Siemen
|
||||
menu.Name=Nimi
|
||||
menu.Content=Sisältö
|
||||
menu.Audio=Audio
|
||||
world.Seed=Siemen
|
||||
world.Name=Nimi
|
||||
|
||||
world.World generator=Maailman generaattori
|
||||
world.generators.default=Tavallinen
|
||||
world.generators.flat=Tasainen
|
||||
menu.Create World=Luo Maailma
|
||||
|
||||
world.World generator=Valon generaattori
|
||||
world.generators.default=Äärimmäinen
|
||||
world.generators.flat=Tasainen
|
||||
world.convert-request=Indeksit vaihdettu! Lataa maailma uudeleen?
|
||||
world.delete-confirm=Poistetaanko maailma ikuisesti?
|
||||
world.convert-request=Indeksit ovat muuttuneet! Luodaanko maailma uudeleen?
|
||||
world.delete-confirm=Poistetaanko maailma peruuttamattomasti?
|
||||
|
||||
# Настройки
|
||||
settings.Load Distance=Latausalue
|
||||
@ -35,6 +41,12 @@ settings.Load Speed=Latausnopeus
|
||||
settings.Fog Curve=Sumun tiheys
|
||||
settings.Backlight=Taustavalo
|
||||
settings.V-Sync=Pystytahdistus
|
||||
settings.Camera Shaking=Kameran tärähdys
|
||||
settings.Master Volume=Pää-äänekkyys
|
||||
settings.Regular Sounds=Tavalliset äänet
|
||||
settings.UI Sounds=Käyttöliittymän äänet
|
||||
settings.Ambient=Tausta
|
||||
settings.Music=Musiikki
|
||||
|
||||
settings.FOV=Näkökenttä
|
||||
settings.Mouse Sensitivity=Hiiren nopeus
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
"be_BY": {
|
||||
"name": "Беларуская"
|
||||
},
|
||||
"de_DE": {
|
||||
"name": "Deutsch"
|
||||
},
|
||||
"en_US": {
|
||||
"name": "English"
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -10,49 +10,53 @@ 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=Вертикальная Синхронизация
|
||||
|
||||
# Управление
|
||||
devtools.console=Консоль
|
||||
movement.forward=Вперёд
|
||||
movement.back=Назад
|
||||
movement.left=Влево
|
||||
@ -67,4 +71,4 @@ player.attack=Атаковать / Сломать
|
||||
player.build=Поставить Блок
|
||||
player.flight=Полёт
|
||||
camera.zoom=Приближение
|
||||
camera.mode=Сменить Режим Камеры
|
||||
camera.mode=Сменить Режим Камеры
|
||||
|
||||
@ -9,46 +9,48 @@ Add=Додати
|
||||
Converting world...=Виконується конвертація світу...
|
||||
|
||||
error.pack-not-found=Не вдалося знайти пакет
|
||||
error.dependency-not-found=Використовувана залежність не знайдена
|
||||
pack.remove-confirm=Видалити назавжди весь контент, що постачається паком, зі світу?
|
||||
|
||||
# Меню
|
||||
menu.New World=Новий Світ
|
||||
menu.Quit=Вихід
|
||||
menu.Continue=Продовжити
|
||||
menu.Save and Quit to Menu=Зберегти і Вийти в Меню
|
||||
menu.missing-content=Відсутній Контент!
|
||||
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=Назва
|
||||
menu.Create World=Створити Світ
|
||||
|
||||
world.World generator=Генератор світу
|
||||
world.generators.default=Звичайний
|
||||
world.generators.flat=Плоский
|
||||
world.generators.default=Звичайні
|
||||
world.generators.flat=Плоскі
|
||||
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.Regular Sounds=Звичайні Звуки
|
||||
settings.UI Sounds=Звуки Інтерфейсу
|
||||
settings.Ambient=Середовище
|
||||
settings.Music=Музика
|
||||
|
||||
settings.FOV=Поле зору
|
||||
settings.Mouse Sensitivity=Чутливість Миші
|
||||
settings.Language=Мова
|
||||
settings.Music=Музика
|
||||
settings.Regular Sounds=Звичайні Звуки
|
||||
settings.UI Sounds=Звуки інтерфейсу
|
||||
settings.V-Sync=Вертикальна Синхронізація
|
||||
|
||||
# Керування
|
||||
movement.forward=Уперед
|
||||
|
||||
BIN
res/textures/gui/circle.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 953 B |
BIN
res/textures/gui/no_world_icon.png
Normal file
|
After Width: | Height: | Size: 510 B |
BIN
res/textures/gui/refresh.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
@ -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<TextureAnimation>& 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<UiDocument>(layout);
|
||||
}
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
#ifndef ASSETS_ASSETS_H_
|
||||
#define ASSETS_ASSETS_H_
|
||||
|
||||
#include "../graphics/TextureAnimation.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Texture;
|
||||
class Shader;
|
||||
class Font;
|
||||
class Atlas;
|
||||
class UiDocument;
|
||||
|
||||
namespace audio {
|
||||
class Sound;
|
||||
}
|
||||
|
||||
class Assets {
|
||||
std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
|
||||
std::unordered_map<std::string, std::shared_ptr<Shader>> shaders;
|
||||
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
|
||||
std::unordered_map<std::string, std::shared_ptr<Atlas>> atlases;
|
||||
std::unordered_map<std::string, std::shared_ptr<UiDocument>> layouts;
|
||||
std::unordered_map<std::string, std::shared_ptr<audio::Sound>> sounds;
|
||||
std::vector<TextureAnimation> 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<TextureAnimation>& 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_ */
|
||||
63
src/assets/Assets.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef ASSETS_ASSETS_HPP_
|
||||
#define ASSETS_ASSETS_HPP_
|
||||
|
||||
#include "../graphics/core/TextureAnimation.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
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<void(Assets*)>;
|
||||
}
|
||||
|
||||
class Assets {
|
||||
std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
|
||||
std::unordered_map<std::string, std::shared_ptr<Shader>> shaders;
|
||||
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
|
||||
std::unordered_map<std::string, std::shared_ptr<Atlas>> atlases;
|
||||
std::unordered_map<std::string, std::shared_ptr<UiDocument>> layouts;
|
||||
std::unordered_map<std::string, std::shared_ptr<audio::Sound>> sounds;
|
||||
std::vector<TextureAnimation> 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<TextureAnimation>& getAnimations();
|
||||
void store(const TextureAnimation& animation);
|
||||
|
||||
UiDocument* getLayout(std::string name) const;
|
||||
void store(UiDocument* layout, std::string name);
|
||||
};
|
||||
|
||||
#endif // ASSETS_ASSETS_HPP_
|
||||
@ -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 <iostream>
|
||||
#include <memory>
|
||||
|
||||
#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<AssetCfg> 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<int>(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<int>(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;
|
||||
}
|
||||
@ -100,7 +116,7 @@ void AssetsLoader::processPreload(
|
||||
switch (tag) {
|
||||
case AssetType::sound:
|
||||
add(tag, path, name, std::make_shared<SoundCfg>(
|
||||
map->getBool("keep-pcm", false)
|
||||
map->get("keep-pcm", false)
|
||||
));
|
||||
break;
|
||||
default:
|
||||
@ -115,13 +131,14 @@ void AssetsLoader::processPreloadList(AssetType tag, dynamic::List* list) {
|
||||
}
|
||||
for (uint i = 0; i < list->size(); i++) {
|
||||
auto value = list->get(i);
|
||||
switch (value->type) {
|
||||
case dynamic::valtype::string:
|
||||
processPreload(tag, *value->value.str, nullptr);
|
||||
switch (static_cast<dynamic::Type>(value.index())) {
|
||||
case dynamic::Type::string:
|
||||
processPreload(tag, std::get<std::string>(value), nullptr);
|
||||
break;
|
||||
case dynamic::valtype::map: {
|
||||
auto name = value->value.map->getStr("name");
|
||||
processPreload(tag, name, value->value.map);
|
||||
case dynamic::Type::map: {
|
||||
auto map = std::get<dynamic::Map_sptr>(value);
|
||||
auto name = map->get<std::string>("name");
|
||||
processPreload(tag, name, map.get());
|
||||
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<std::filesystem::path> 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<aloader_entry, assetload::postfunc> {
|
||||
AssetsLoader* loader;
|
||||
public:
|
||||
LoaderWorker(AssetsLoader* loader) : loader(loader) {
|
||||
}
|
||||
|
||||
assetload::postfunc operator()(const std::shared_ptr<aloader_entry>& entry) override {
|
||||
aloader_func loadfunc = loader->getLoader(entry->tag);
|
||||
return loadfunc(loader, loader->getPaths(), entry->filename, entry->alias, entry->config);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<Task> AssetsLoader::startTask(runnable onDone) {
|
||||
auto pool = std::make_shared<
|
||||
util::ThreadPool<aloader_entry, assetload::postfunc>
|
||||
>(
|
||||
"assets-loader-pool",
|
||||
[=](){return std::make_shared<LoaderWorker>(this);},
|
||||
[=](assetload::postfunc& func) {
|
||||
func(assets);
|
||||
}
|
||||
);
|
||||
pool->setOnComplete(onDone);
|
||||
while (!entries.empty()) {
|
||||
const aloader_entry& entry = entries.front();
|
||||
auto ptr = std::make_shared<aloader_entry>(entry);
|
||||
pool->enqueueJob(ptr);
|
||||
entries.pop();
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
#ifndef ASSETS_ASSETS_LOADER_H
|
||||
#define ASSETS_ASSETS_LOADER_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
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<bool(AssetsLoader&, Assets*, const ResPaths*, const std::string&, const std::string&, std::shared_ptr<AssetCfg>)>;
|
||||
|
||||
struct aloader_entry {
|
||||
AssetType tag;
|
||||
const std::string filename;
|
||||
const std::string alias;
|
||||
std::shared_ptr<AssetCfg> config;
|
||||
};
|
||||
|
||||
class AssetsLoader {
|
||||
Assets* assets;
|
||||
std::map<AssetType, aloader_func> loaders;
|
||||
std::queue<aloader_entry> 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<AssetCfg> 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
|
||||
113
src/assets/AssetsLoader.hpp
Normal file
@ -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 <string>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
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<assetload::postfunc(
|
||||
AssetsLoader*, // redundant?
|
||||
const ResPaths*,
|
||||
const std::string&,
|
||||
const std::string&,
|
||||
std::shared_ptr<AssetCfg>)
|
||||
>;
|
||||
|
||||
struct aloader_entry {
|
||||
AssetType tag;
|
||||
const std::string filename;
|
||||
const std::string alias;
|
||||
std::shared_ptr<AssetCfg> config;
|
||||
};
|
||||
|
||||
class AssetsLoader {
|
||||
Assets* assets;
|
||||
std::map<AssetType, aloader_func> loaders;
|
||||
std::queue<aloader_entry> 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<AssetCfg> settings=nullptr
|
||||
);
|
||||
|
||||
bool hasNext() const;
|
||||
bool loadNext();
|
||||
|
||||
std::shared_ptr<Task> 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<std::filesystem::path> alternatives
|
||||
);
|
||||
};
|
||||
|
||||
#endif // ASSETS_ASSETS_LOADER_HPP_
|
||||
@ -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 <iostream>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
#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<AssetCfg>
|
||||
) {
|
||||
std::unique_ptr<Texture> texture(
|
||||
png::load_texture(paths->find(filename+".png").u8string())
|
||||
std::shared_ptr<ImageData> 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<ImageData> 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<std::string> 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<AssetCfg>
|
||||
) {
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
auto pages = std::make_shared<std::vector<std::unique_ptr<ImageData>>>();
|
||||
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> 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<std::unique_ptr<Texture>> 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<AssetCfg> config
|
||||
) {
|
||||
try {
|
||||
auto cfg = dynamic_cast<LayoutCfg*>(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<LayoutCfg>(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<AssetCfg> config
|
||||
) {
|
||||
auto cfg = dynamic_cast<SoundCfg*>(config.get());
|
||||
auto cfg = std::dynamic_pointer_cast<SoundCfg>(config);
|
||||
bool keepPCM = cfg ? cfg->keepPCM : false;
|
||||
|
||||
std::string extension = ".ogg";
|
||||
try {
|
||||
std::unique_ptr<audio::Sound> baseSound = nullptr;
|
||||
std::unique_ptr<audio::Sound> 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 = 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 = 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<std::pair<std::string, int>>& 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<std::string>& frameNames,
|
||||
const std::vector<std::pair<std::string, int>>& 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<float>(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<std::pair<std::string, int>>& 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<std::pair<std::string, float>> frameList;
|
||||
append_atlas(builder, paths->find(directory + "/" + name + ".png"));
|
||||
|
||||
std::vector<std::pair<std::string, int>> 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<float>(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<Atlas> 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;
|
||||
|
||||
@ -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 <string>
|
||||
#include <memory>
|
||||
@ -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<AssetCfg> settings
|
||||
);
|
||||
bool shader(
|
||||
AssetsLoader&,
|
||||
Assets*,
|
||||
postfunc shader(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string filename,
|
||||
const std::string name,
|
||||
std::shared_ptr<AssetCfg> settings
|
||||
);
|
||||
bool atlas(
|
||||
AssetsLoader&,
|
||||
Assets*,
|
||||
postfunc atlas(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string directory,
|
||||
const std::string name,
|
||||
std::shared_ptr<AssetCfg> settings
|
||||
);
|
||||
bool font(
|
||||
AssetsLoader&,
|
||||
Assets*,
|
||||
postfunc font(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string filename,
|
||||
const std::string name,
|
||||
std::shared_ptr<AssetCfg> settings
|
||||
);
|
||||
bool layout(
|
||||
AssetsLoader&,
|
||||
Assets*,
|
||||
postfunc layout(
|
||||
AssetsLoader*,
|
||||
const ResPaths* paths,
|
||||
const std::string file,
|
||||
const std::string name,
|
||||
std::shared_ptr<AssetCfg> 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_
|
||||
@ -1,7 +1,11 @@
|
||||
#include "ALAudio.h"
|
||||
#include "alutil.h"
|
||||
#include "ALAudio.hpp"
|
||||
|
||||
#include "alutil.hpp"
|
||||
#include "../../debug/Logger.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
static debug::Logger logger("al-audio");
|
||||
|
||||
using namespace audio;
|
||||
|
||||
@ -19,14 +23,14 @@ ALSound::~ALSound() {
|
||||
buffer = 0;
|
||||
}
|
||||
|
||||
Speaker* ALSound::newInstance(int priority, int channel) const {
|
||||
std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
|
||||
uint source = al->getFreeSource();
|
||||
if (source == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
AL_CHECK(alSourcei(source, AL_BUFFER, buffer));
|
||||
|
||||
auto speaker = new ALSpeaker(al, source, priority, channel);
|
||||
auto speaker = std::make_unique<ALSpeaker>(al, source, priority, channel);
|
||||
speaker->duration = duration;
|
||||
return speaker;
|
||||
}
|
||||
@ -62,7 +66,7 @@ bool ALStream::preloadBuffer(uint buffer, bool loop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Speaker* ALStream::createSpeaker(bool loop, int channel) {
|
||||
std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
|
||||
this->loop = loop;
|
||||
uint source = al->getFreeSource();
|
||||
if (source == 0) {
|
||||
@ -75,7 +79,7 @@ Speaker* ALStream::createSpeaker(bool loop, int channel) {
|
||||
}
|
||||
AL_CHECK(alSourceQueueBuffers(source, 1, &buffer));
|
||||
}
|
||||
return new ALSpeaker(al, source, PRIORITY_HIGH, channel);
|
||||
return std::make_unique<ALSpeaker>(al, source, PRIORITY_HIGH, channel);
|
||||
}
|
||||
|
||||
|
||||
@ -203,10 +207,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) {
|
||||
@ -259,7 +263,7 @@ void ALSpeaker::play() {
|
||||
paused = false;
|
||||
stopped = false;
|
||||
auto channel = get_channel(this->channel);
|
||||
AL_CHECK(alSourcef(source, AL_GAIN, volume * channel->getVolume()));
|
||||
AL_CHECK(alSourcef(source, AL_GAIN, volume * channel->getVolume() * get_channel(0)->getVolume()));
|
||||
AL_CHECK(alSourcePlay(source));
|
||||
}
|
||||
|
||||
@ -341,14 +345,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,21 +372,21 @@ 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;
|
||||
}
|
||||
|
||||
Sound* ALAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
std::unique_ptr<Sound> ALAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
auto format = AL::to_al_format(pcm->channels, pcm->bitsPerSample);
|
||||
uint buffer = getFreeBuffer();
|
||||
AL_CHECK(alBufferData(buffer, format, pcm->data.data(), pcm->data.size(), pcm->sampleRate));
|
||||
return new ALSound(this, buffer, pcm, keepPCM);
|
||||
return std::make_unique<ALSound>(this, buffer, pcm, keepPCM);
|
||||
}
|
||||
|
||||
Stream* ALAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
return new ALStream(this, stream, keepSource);
|
||||
std::unique_ptr<Stream> ALAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
return std::make_unique<ALStream>(this, stream, keepSource);
|
||||
}
|
||||
|
||||
ALAudio* ALAudio::create() {
|
||||
@ -395,7 +399,7 @@ ALAudio* ALAudio::create() {
|
||||
return nullptr;
|
||||
}
|
||||
AL_CHECK();
|
||||
std::cout << "AL: initialized" << std::endl;
|
||||
logger.info() << "initialized";
|
||||
return new ALAudio(device, context);
|
||||
}
|
||||
|
||||
@ -406,7 +410,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,7 +471,8 @@ 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) {
|
||||
void ALAudio::update(double) {
|
||||
}
|
||||
|
||||
@ -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 <queue>
|
||||
#include <vector>
|
||||
@ -15,9 +18,6 @@
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
#include "../audio.h"
|
||||
#include "../../typedefs.h"
|
||||
|
||||
namespace audio {
|
||||
struct ALBuffer;
|
||||
class ALAudio;
|
||||
@ -40,7 +40,7 @@ namespace audio {
|
||||
return pcm;
|
||||
}
|
||||
|
||||
Speaker* newInstance(int priority, int channel) const override;
|
||||
std::unique_ptr<Speaker> newInstance(int priority, int channel) const override;
|
||||
};
|
||||
|
||||
class ALStream : public Stream {
|
||||
@ -65,7 +65,7 @@ namespace audio {
|
||||
|
||||
std::shared_ptr<PCMStream> getSource() const override;
|
||||
void bindSpeaker(speakerid_t speaker) override;
|
||||
Speaker* createSpeaker(bool loop, int channel) override;
|
||||
std::unique_ptr<Speaker> createSpeaker(bool loop, int channel) override;
|
||||
speakerid_t getSpeaker() const override;
|
||||
void update(double delta) override;
|
||||
|
||||
@ -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;
|
||||
@ -135,7 +135,7 @@ namespace audio {
|
||||
std::vector<uint> allbuffers;
|
||||
std::vector<uint> freebuffers;
|
||||
|
||||
uint maxSources;
|
||||
uint maxSources = 256;
|
||||
|
||||
ALAudio(ALCdevice* device, ALCcontext* context);
|
||||
public:
|
||||
@ -148,8 +148,8 @@ namespace audio {
|
||||
|
||||
std::vector<std::string> getAvailableDevices() const;
|
||||
|
||||
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
|
||||
Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
|
||||
std::unique_ptr<Sound> createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
|
||||
std::unique_ptr<Stream> openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
|
||||
|
||||
void setListener(
|
||||
glm::vec3 position,
|
||||
@ -168,4 +168,4 @@ namespace audio {
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* SRC_AUDIO_AUDIO_H_ */
|
||||
#endif // SRC_AUDIO_AUDIO_HPP_
|
||||
@ -1,6 +1,7 @@
|
||||
#include "alutil.h"
|
||||
#include "alutil.hpp"
|
||||
|
||||
#include "../../debug/Logger.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@ -14,30 +15,31 @@
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
static debug::Logger logger("open-al");
|
||||
|
||||
bool AL::check_errors(const std::string& filename, const std::uint_fast32_t line){
|
||||
ALenum error = alGetError();
|
||||
if(error != AL_NO_ERROR){
|
||||
std::cerr << "OpenAL ERROR (" << filename << ": " << line << ")\n" ;
|
||||
logger.error() << filename << ": " << line;
|
||||
switch(error){
|
||||
case AL_INVALID_NAME:
|
||||
std::cerr << "AL_INVALID_NAME: a bad name (ID) was passed to an OpenAL function";
|
||||
logger.error() << "a bad name (ID) was passed to an OpenAL function";
|
||||
break;
|
||||
case AL_INVALID_ENUM:
|
||||
std::cerr << "AL_INVALID_ENUM: an invalid enum value was passed to an OpenAL function";
|
||||
logger.error() << "an invalid enum value was passed to an OpenAL function";
|
||||
break;
|
||||
case AL_INVALID_VALUE:
|
||||
std::cerr << "AL_INVALID_VALUE: an invalid value was passed to an OpenAL function";
|
||||
logger.error() << "an invalid value was passed to an OpenAL function";
|
||||
break;
|
||||
case AL_INVALID_OPERATION:
|
||||
std::cerr << "AL_INVALID_OPERATION: the requested operation is not valid";
|
||||
logger.error() << "the requested operation is not valid";
|
||||
break;
|
||||
case AL_OUT_OF_MEMORY:
|
||||
std::cerr << "AL_OUT_OF_MEMORY: the requested operation resulted in OpenAL running out of memory";
|
||||
logger.error() << "the requested operation resulted in OpenAL running out of memory";
|
||||
break;
|
||||
default:
|
||||
std::cerr << "UNKNOWN AL ERROR: " << error;
|
||||
logger.error() << "UNKNOWN AL ERROR: " << error;
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -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 <string>
|
||||
#include <type_traits>
|
||||
@ -12,7 +14,6 @@
|
||||
#endif
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#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_
|
||||
@ -1,4 +1,4 @@
|
||||
#include "NoAudio.h"
|
||||
#include "NoAudio.hpp"
|
||||
|
||||
using namespace audio;
|
||||
|
||||
@ -9,12 +9,12 @@ NoSound::NoSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
}
|
||||
}
|
||||
|
||||
Sound* NoAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
return new NoSound(pcm, keepPCM);
|
||||
std::unique_ptr<Sound> NoAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
return std::make_unique<NoSound>(pcm, keepPCM);
|
||||
}
|
||||
|
||||
Stream* NoAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
return new NoStream(stream, keepSource);
|
||||
std::unique_ptr<Stream> NoAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
return std::make_unique<NoStream>(stream, keepSource);
|
||||
}
|
||||
|
||||
NoAudio* NoAudio::create() {
|
||||
|
||||
@ -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 {
|
||||
@ -19,7 +19,7 @@ namespace audio {
|
||||
return pcm;
|
||||
}
|
||||
|
||||
Speaker* newInstance(int priority, int channel) const override {
|
||||
std::unique_ptr<Speaker> newInstance(int priority, int channel) const override {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
@ -42,7 +42,7 @@ namespace audio {
|
||||
void bindSpeaker(speakerid_t speaker) override {
|
||||
}
|
||||
|
||||
Speaker* createSpeaker(bool loop, int channel) override{
|
||||
std::unique_ptr<Speaker> createSpeaker(bool loop, int channel) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -65,8 +65,8 @@ namespace audio {
|
||||
public:
|
||||
~NoAudio() {}
|
||||
|
||||
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
|
||||
Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
|
||||
std::unique_ptr<Sound> createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
|
||||
std::unique_ptr<Stream> openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
|
||||
|
||||
void setListener(
|
||||
glm::vec3 position,
|
||||
@ -85,4 +85,4 @@ namespace audio {
|
||||
};
|
||||
}
|
||||
|
||||
#endif // AUDIO_NOAUDIO_H_
|
||||
#endif // AUDIO_NOAUDIO_HPP_
|
||||
@ -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 <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#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;
|
||||
@ -72,9 +72,6 @@ size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) {
|
||||
if (loop) {
|
||||
seek(0);
|
||||
}
|
||||
if (bufferSize == 0) {
|
||||
return size;
|
||||
}
|
||||
} while (loop);
|
||||
return size;
|
||||
}
|
||||
@ -94,7 +91,7 @@ public:
|
||||
seekable(seekable)
|
||||
{}
|
||||
|
||||
size_t read(char* buffer, size_t bufferSize) override {
|
||||
size_t read(char*, size_t bufferSize) override {
|
||||
if (closed) {
|
||||
return 0;
|
||||
}
|
||||
@ -159,7 +156,7 @@ void audio::initialize(bool enabled) {
|
||||
create_channel("master");
|
||||
}
|
||||
|
||||
PCM* audio::load_PCM(const fs::path& file, bool headerOnly) {
|
||||
std::unique_ptr<PCM> audio::load_PCM(const fs::path& file, bool headerOnly) {
|
||||
if (!fs::exists(file)) {
|
||||
throw std::runtime_error("file not found '"+file.u8string()+"'");
|
||||
}
|
||||
@ -172,16 +169,16 @@ PCM* audio::load_PCM(const fs::path& file, bool headerOnly) {
|
||||
throw std::runtime_error("unsupported audio format");
|
||||
}
|
||||
|
||||
Sound* audio::load_sound(const fs::path& file, bool keepPCM) {
|
||||
std::shared_ptr<PCM> pcm(load_PCM(file, !keepPCM && backend->isDummy()));
|
||||
std::unique_ptr<Sound> audio::load_sound(const fs::path& file, bool keepPCM) {
|
||||
std::shared_ptr<PCM> pcm(load_PCM(file, !keepPCM && backend->isDummy()).release());
|
||||
return create_sound(pcm, keepPCM);
|
||||
}
|
||||
|
||||
Sound* audio::create_sound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
std::unique_ptr<Sound> audio::create_sound(std::shared_ptr<PCM> pcm, bool keepPCM) {
|
||||
return backend->createSound(pcm, keepPCM);
|
||||
}
|
||||
|
||||
PCMStream* audio::open_PCM_stream(const fs::path& file) {
|
||||
std::unique_ptr<PCMStream> audio::open_PCM_stream(const fs::path& file) {
|
||||
std::string ext = file.extension().u8string();
|
||||
if (ext == ".wav" || ext == ".WAV") {
|
||||
return wav::create_stream(file);
|
||||
@ -191,7 +188,7 @@ PCMStream* audio::open_PCM_stream(const fs::path& file) {
|
||||
throw std::runtime_error("unsupported audio stream format");
|
||||
}
|
||||
|
||||
Stream* audio::open_stream(const fs::path& file, bool keepSource) {
|
||||
std::unique_ptr<Stream> audio::open_stream(const fs::path& file, bool keepSource) {
|
||||
if (!keepSource && backend->isDummy()) {
|
||||
auto header = load_PCM(file, true);
|
||||
// using void source sized as audio instead of actual audio file
|
||||
@ -206,7 +203,7 @@ Stream* audio::open_stream(const fs::path& file, bool keepSource) {
|
||||
);
|
||||
}
|
||||
|
||||
Stream* audio::open_stream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
std::unique_ptr<Stream> audio::open_stream(std::shared_ptr<PCMStream> stream, bool keepSource) {
|
||||
return backend->openStream(stream, keepSource);
|
||||
}
|
||||
|
||||
@ -258,16 +255,17 @@ speakerid_t audio::play(
|
||||
sound = sound->variants.at(index).get();
|
||||
}
|
||||
}
|
||||
Speaker* speaker = sound->newInstance(priority, channel);
|
||||
if (speaker == nullptr) {
|
||||
auto speaker_ptr = sound->newInstance(priority, channel);
|
||||
if (speaker_ptr == nullptr) {
|
||||
remove_lower_priority_speaker(priority);
|
||||
speaker = sound->newInstance(priority, channel);
|
||||
speaker_ptr = sound->newInstance(priority, channel);
|
||||
}
|
||||
if (speaker == nullptr) {
|
||||
if (speaker_ptr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
auto speaker = speaker_ptr.get();
|
||||
speakerid_t id = nextId++;
|
||||
speakers.emplace(id, speaker);
|
||||
speakers.emplace(id, std::move(speaker_ptr));
|
||||
speaker->setPosition(position);
|
||||
speaker->setVolume(volume);
|
||||
speaker->setPitch(pitch);
|
||||
@ -286,17 +284,18 @@ speakerid_t audio::play(
|
||||
bool loop,
|
||||
int channel
|
||||
) {
|
||||
Speaker* speaker = stream->createSpeaker(loop, channel);
|
||||
if (speaker == nullptr) {
|
||||
auto speaker_ptr = stream->createSpeaker(loop, channel);
|
||||
if (speaker_ptr == nullptr) {
|
||||
remove_lower_priority_speaker(PRIORITY_HIGH);
|
||||
speaker = stream->createSpeaker(loop, channel);
|
||||
speaker_ptr = stream->createSpeaker(loop, channel);
|
||||
}
|
||||
if (speaker == nullptr) {
|
||||
if (speaker_ptr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
auto speaker = speaker_ptr.get();
|
||||
speakerid_t id = nextId++;
|
||||
streams.emplace(id, stream);
|
||||
speakers.emplace(id, speaker);
|
||||
speakers.emplace(id, std::move(speaker_ptr));
|
||||
stream->bindSpeaker(id);
|
||||
|
||||
speaker->setPosition(position);
|
||||
@ -383,13 +382,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);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
#ifndef AUDIO_AUDIO_H_
|
||||
#define AUDIO_AUDIO_H_
|
||||
#ifndef AUDIO_AUDIO_HPP_
|
||||
#define AUDIO_AUDIO_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <glm/glm.hpp>
|
||||
#include "../typedefs.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@ -178,7 +179,7 @@ namespace audio {
|
||||
/// @param loop is stream looped (required for correct buffers preload)
|
||||
/// @param channel channel index
|
||||
/// @return speaker id or 0
|
||||
virtual Speaker* createSpeaker(bool loop, int channel) = 0;
|
||||
virtual std::unique_ptr<Speaker> createSpeaker(bool loop, int channel) = 0;
|
||||
|
||||
/// @brief Unbind previous speaker and bind new speaker to the stream
|
||||
/// @param speaker speaker id or 0 if all you need is unbind speaker
|
||||
@ -224,7 +225,7 @@ namespace audio {
|
||||
/// @param channel channel index
|
||||
/// @return new speaker with sound bound or nullptr
|
||||
/// if all speakers are in use
|
||||
virtual Speaker* newInstance(int priority, int channel) const = 0;
|
||||
virtual std::unique_ptr<Speaker> newInstance(int priority, int channel) const = 0;
|
||||
};
|
||||
|
||||
/// @brief Audio source controller interface.
|
||||
@ -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;
|
||||
@ -336,8 +336,8 @@ namespace audio {
|
||||
public:
|
||||
virtual ~Backend() {};
|
||||
|
||||
virtual Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) = 0;
|
||||
virtual Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) = 0;
|
||||
virtual std::unique_ptr<Sound> createSound(std::shared_ptr<PCM> pcm, bool keepPCM) = 0;
|
||||
virtual std::unique_ptr<Stream> openStream(std::shared_ptr<PCMStream> stream, bool keepSource) = 0;
|
||||
virtual void setListener(
|
||||
glm::vec3 position,
|
||||
glm::vec3 velocity,
|
||||
@ -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);
|
||||
std::unique_ptr<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);
|
||||
std::unique_ptr<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> pcm, bool keepPCM);
|
||||
std::unique_ptr<Sound> create_sound(std::shared_ptr<PCM> 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);
|
||||
std::unique_ptr<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);
|
||||
std::unique_ptr<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<PCMStream> stream, bool keepSource);
|
||||
std::unique_ptr<Stream> open_stream(std::shared_ptr<PCMStream> 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> 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<Stream> get_associated_stream(speakerid_t id);
|
||||
std::shared_ptr<Stream> 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_
|
||||
@ -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 <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#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;
|
||||
|
||||
@ -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 <string>
|
||||
#include <vector>
|
||||
@ -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_
|
||||
@ -1,29 +1,32 @@
|
||||
#include "binary_json.h"
|
||||
#include "binary_json.hpp"
|
||||
|
||||
#include "gzip.hpp"
|
||||
#include "byte_utils.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#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::map: {
|
||||
std::vector<ubyte> bytes = to_binary(value->value.map);
|
||||
static void to_binary(ByteBuilder& builder, const Value& value) {
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::none:
|
||||
throw std::runtime_error("none value is not implemented");
|
||||
case Type::map: {
|
||||
auto bytes = to_binary(std::get<Map_sptr>(value).get());
|
||||
builder.put(bytes.data(), bytes.size());
|
||||
break;
|
||||
}
|
||||
case valtype::list:
|
||||
case Type::list:
|
||||
builder.put(BJSON_TYPE_LIST);
|
||||
for (auto& element : value->value.list->values) {
|
||||
to_binary(builder, element.get());
|
||||
for (auto& element : std::get<List_sptr>(value)->values) {
|
||||
to_binary(builder, element);
|
||||
}
|
||||
builder.put(BJSON_END);
|
||||
break;
|
||||
case valtype::integer: {
|
||||
int64_t val = value->value.integer;
|
||||
case Type::integer: {
|
||||
auto val = std::get<integer_t>(value);
|
||||
if (val >= 0 && val <= 255) {
|
||||
builder.put(BJSON_TYPE_BYTE);
|
||||
builder.put(val);
|
||||
@ -39,22 +42,22 @@ static void to_binary(ByteBuilder& builder, const Value* value) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case valtype::number:
|
||||
case Type::number:
|
||||
builder.put(BJSON_TYPE_NUMBER);
|
||||
builder.putFloat64(value->value.decimal);
|
||||
builder.putFloat64(std::get<number_t>(value));
|
||||
break;
|
||||
case valtype::boolean:
|
||||
builder.put(BJSON_TYPE_FALSE + value->value.boolean);
|
||||
case Type::boolean:
|
||||
builder.put(BJSON_TYPE_FALSE + std::get<bool>(value));
|
||||
break;
|
||||
case valtype::string:
|
||||
case Type::string:
|
||||
builder.put(BJSON_TYPE_STRING);
|
||||
builder.put(*value->value.str);
|
||||
builder.put(std::get<std::string>(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static List* array_from_binary(ByteReader& reader);
|
||||
static Map* object_from_binary(ByteReader& reader);
|
||||
static std::unique_ptr<List> array_from_binary(ByteReader& reader);
|
||||
static std::unique_ptr<Map> object_from_binary(ByteReader& reader);
|
||||
|
||||
std::vector<ubyte> json::to_binary(const Map* obj, bool compress) {
|
||||
if (compress) {
|
||||
@ -70,7 +73,7 @@ std::vector<ubyte> json::to_binary(const Map* obj, bool compress) {
|
||||
// writing entries
|
||||
for (auto& entry : obj->values) {
|
||||
builder.putCStr(entry.first.c_str());
|
||||
to_binary(builder, entry.second.get());
|
||||
to_binary(builder, entry.second);
|
||||
}
|
||||
// terminating byte
|
||||
builder.put(BJSON_END);
|
||||
@ -80,79 +83,56 @@ std::vector<ubyte> json::to_binary(const Map* obj, bool compress) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static Value* value_from_binary(ByteReader& reader) {
|
||||
static Value value_from_binary(ByteReader& reader) {
|
||||
ubyte typecode = reader.get();
|
||||
valtype type;
|
||||
valvalue val;
|
||||
switch (typecode) {
|
||||
case BJSON_TYPE_DOCUMENT:
|
||||
type = valtype::map;
|
||||
reader.getInt32();
|
||||
val.map = object_from_binary(reader);
|
||||
break;
|
||||
return Map_sptr(object_from_binary(reader).release());
|
||||
case BJSON_TYPE_LIST:
|
||||
type = valtype::list;
|
||||
val.list = array_from_binary(reader);
|
||||
break;
|
||||
return List_sptr(array_from_binary(reader).release());
|
||||
case BJSON_TYPE_BYTE:
|
||||
type = valtype::integer;
|
||||
val.integer = reader.get();
|
||||
break;
|
||||
return static_cast<integer_t>(reader.get());
|
||||
case BJSON_TYPE_INT16:
|
||||
type = valtype::integer;
|
||||
val.integer = reader.getInt16();
|
||||
break;
|
||||
return static_cast<integer_t>(reader.getInt16());
|
||||
case BJSON_TYPE_INT32:
|
||||
type = valtype::integer;
|
||||
val.integer = reader.getInt32();
|
||||
break;
|
||||
return static_cast<integer_t>(reader.getInt32());
|
||||
case BJSON_TYPE_INT64:
|
||||
type = valtype::integer;
|
||||
val.integer = reader.getInt64();
|
||||
break;
|
||||
return reader.getInt64();
|
||||
case BJSON_TYPE_NUMBER:
|
||||
type = valtype::number;
|
||||
val.decimal = reader.getFloat64();
|
||||
break;
|
||||
return reader.getFloat64();
|
||||
case BJSON_TYPE_FALSE:
|
||||
case BJSON_TYPE_TRUE:
|
||||
type = valtype::boolean;
|
||||
val.boolean = typecode - BJSON_TYPE_FALSE;
|
||||
break;
|
||||
return (typecode - BJSON_TYPE_FALSE) != 0;
|
||||
case BJSON_TYPE_STRING:
|
||||
type = valtype::string;
|
||||
val.str = new std::string(reader.getString());
|
||||
break;
|
||||
return reader.getString();
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"type "+std::to_string(typecode)+" is not supported");
|
||||
"type "+std::to_string(typecode)+" is not supported"
|
||||
);
|
||||
}
|
||||
return new Value(type, val);
|
||||
}
|
||||
|
||||
static List* array_from_binary(ByteReader& reader) {
|
||||
static std::unique_ptr<List> array_from_binary(ByteReader& reader) {
|
||||
auto array = std::make_unique<List>();
|
||||
auto& items = array->values;
|
||||
while (reader.peek() != BJSON_END) {
|
||||
items.push_back(std::unique_ptr<Value>(value_from_binary(reader)));
|
||||
array->put(value_from_binary(reader));
|
||||
}
|
||||
reader.get();
|
||||
return array.release();
|
||||
return array;
|
||||
}
|
||||
|
||||
static Map* object_from_binary(ByteReader& reader) {
|
||||
static std::unique_ptr<Map> object_from_binary(ByteReader& reader) {
|
||||
auto obj = std::make_unique<Map>();
|
||||
auto& map = obj->values;
|
||||
while (reader.peek() != BJSON_END) {
|
||||
const char* key = reader.getCString();
|
||||
Value* value = value_from_binary(reader);
|
||||
map.insert(std::make_pair(key, value));
|
||||
obj->put(key, value_from_binary(reader));
|
||||
}
|
||||
reader.get();
|
||||
return obj.release();
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::unique_ptr<Map> json::from_binary(const ubyte* src, size_t size) {
|
||||
std::shared_ptr<Map> json::from_binary(const ubyte* src, size_t size) {
|
||||
if (size < 2) {
|
||||
throw std::runtime_error("bytes length is less than 2");
|
||||
}
|
||||
@ -162,12 +142,12 @@ std::unique_ptr<Map> json::from_binary(const ubyte* src, size_t size) {
|
||||
return from_binary(data.data(), data.size());
|
||||
} else {
|
||||
ByteReader reader(src, size);
|
||||
std::unique_ptr<Value> value (value_from_binary(reader));
|
||||
if (value->type != valtype::map) {
|
||||
Value value = value_from_binary(reader);
|
||||
|
||||
if (auto map = std::get_if<Map_sptr>(&value)) {
|
||||
return *map;
|
||||
} else {
|
||||
throw std::runtime_error("root value is not an object");
|
||||
}
|
||||
std::unique_ptr<Map> obj (value->value.map);
|
||||
value->value.map = nullptr;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 <vector>
|
||||
#include <memory>
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
namespace dynamic {
|
||||
class Map;
|
||||
}
|
||||
|
||||
namespace json {
|
||||
const int BJSON_END = 0x0;
|
||||
@ -22,7 +27,7 @@ namespace json {
|
||||
const int BJSON_TYPE_CDOCUMENT = 0x1F;
|
||||
|
||||
extern std::vector<ubyte> to_binary(const dynamic::Map* obj, bool compress=false);
|
||||
extern std::unique_ptr<dynamic::Map> from_binary(const ubyte* src, size_t size);
|
||||
extern std::shared_ptr<dynamic::Map> from_binary(const ubyte* src, size_t size);
|
||||
}
|
||||
|
||||
#endif // CODERS_BINARY_JSON_H_
|
||||
#endif // CODERS_BINARY_JSON_HPP_
|
||||
@ -1,4 +1,4 @@
|
||||
#include "byte_utils.h"
|
||||
#include "byte_utils.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
@ -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 <string>
|
||||
#include <vector>
|
||||
#include "../typedefs.h"
|
||||
|
||||
/* byteorder: little-endian */
|
||||
class ByteBuilder {
|
||||
@ -44,7 +45,7 @@ public:
|
||||
std::vector<ubyte> 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_
|
||||
@ -1,344 +1,337 @@
|
||||
#include "commons.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <math.h>
|
||||
|
||||
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 <sstream>
|
||||
#include <stdexcept>
|
||||
#include <math.h>
|
||||
|
||||
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_view filename,
|
||||
std::string_view source,
|
||||
uint pos,
|
||||
uint line,
|
||||
uint linestart
|
||||
) : std::runtime_error(message), filename(filename),
|
||||
pos(pos), line(line), linestart(linestart)
|
||||
{
|
||||
size_t end = source.find("\n", linestart);
|
||||
if (end == std::string::npos) {
|
||||
end = source.length();
|
||||
}
|
||||
this->source = source.substr(linestart, end-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";
|
||||
ss << source << "\n";
|
||||
for (uint i = 0; i < linepos; i++) {
|
||||
ss << " ";
|
||||
}
|
||||
ss << "^";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
BasicParser::BasicParser(
|
||||
std::string_view file,
|
||||
std::string_view 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(size_t count) {
|
||||
if (pos < count) {
|
||||
throw std::runtime_error("pos < jump");
|
||||
}
|
||||
if (pos) {
|
||||
pos -= count;
|
||||
}
|
||||
}
|
||||
|
||||
char BasicParser::peek() {
|
||||
skipWhitespace();
|
||||
if (pos >= source.length()) {
|
||||
throw error("unexpected end");
|
||||
}
|
||||
return source[pos];
|
||||
}
|
||||
|
||||
char BasicParser::peekNoJump() {
|
||||
if (pos >= source.length()) {
|
||||
throw error("unexpected end");
|
||||
}
|
||||
return source[pos];
|
||||
}
|
||||
|
||||
std::string_view 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 std::string(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;
|
||||
}
|
||||
|
||||
dynamic::Value BasicParser::parseNumber(int sign) {
|
||||
char c = peek();
|
||||
int base = 10;
|
||||
if (c == '0' && pos + 1 < source.length() &&
|
||||
(base = is_box(source[pos+1])) != 10) {
|
||||
pos += 2;
|
||||
return parseSimpleInt(base);
|
||||
} else if (c == 'i' && pos + 2 < source.length() && source[pos+1] == 'n' && source[pos+2] == 'f') {
|
||||
pos += 3;
|
||||
return INFINITY * sign;
|
||||
} else if (c == 'n' && pos + 2 < source.length() && source[pos+1] == 'a' && source[pos+2] == 'n') {
|
||||
pos += 3;
|
||||
return NAN * sign;
|
||||
}
|
||||
int64_t value = parseSimpleInt(base);
|
||||
if (!hasNext()) {
|
||||
return value * sign;
|
||||
}
|
||||
c = source[pos];
|
||||
if (c == 'e' || c == 'E') {
|
||||
pos++;
|
||||
int s = 1;
|
||||
if (peek() == '-') {
|
||||
s = -1;
|
||||
pos++;
|
||||
} else if (peek() == '+'){
|
||||
pos++;
|
||||
}
|
||||
return sign * value * power(10.0, s * parseSimpleInt(10));
|
||||
}
|
||||
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++;
|
||||
}
|
||||
return sign * dvalue * power(10.0, s * parseSimpleInt(10));
|
||||
}
|
||||
return sign * dvalue;
|
||||
}
|
||||
return sign * value;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
#ifndef CODERS_COMMONS_H_
|
||||
#define CODERS_COMMONS_H_
|
||||
#ifndef CODERS_COMMONS_HPP_
|
||||
#define CODERS_COMMONS_HPP_
|
||||
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include "../typedefs.h"
|
||||
|
||||
union number_u {
|
||||
double fval;
|
||||
int64_t ival;
|
||||
};
|
||||
|
||||
inline int is_box(int c) {
|
||||
switch (c) {
|
||||
@ -34,11 +31,11 @@ inline bool is_whitespace(int c) {
|
||||
}
|
||||
|
||||
inline bool is_identifier_start(int c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '.';
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '.';
|
||||
}
|
||||
|
||||
inline bool is_identifier_part(int c) {
|
||||
return is_identifier_start(c) || is_digit(c);
|
||||
return is_identifier_start(c) || is_digit(c) || c == '-';
|
||||
}
|
||||
|
||||
inline int hexchar2int(int c) {
|
||||
@ -54,8 +51,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 +59,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_view filename,
|
||||
std::string_view source,
|
||||
uint pos,
|
||||
uint line,
|
||||
uint linestart
|
||||
);
|
||||
std::string errorLog() const;
|
||||
};
|
||||
|
||||
class BasicParser {
|
||||
protected:
|
||||
std::string filename;
|
||||
std::string source;
|
||||
std::string_view filename;
|
||||
std::string_view source;
|
||||
uint pos = 0;
|
||||
uint line = 1;
|
||||
uint linestart = 0;
|
||||
@ -88,21 +84,25 @@ 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();
|
||||
void goBack(size_t count=1);
|
||||
|
||||
std::string parseName();
|
||||
int64_t parseSimpleInt(int base);
|
||||
bool parseNumber(int sign, number_u& out);
|
||||
dynamic::Value parseNumber(int sign);
|
||||
std::string parseString(char chr, bool closeRequired=true);
|
||||
|
||||
parsing_error error(std::string message);
|
||||
|
||||
BasicParser(std::string filename, std::string source);
|
||||
public:
|
||||
std::string_view readUntil(char c);
|
||||
std::string parseName();
|
||||
bool hasNext();
|
||||
char peek();
|
||||
char peekNoJump();
|
||||
char nextChar();
|
||||
|
||||
BasicParser(std::string_view file, std::string_view source);
|
||||
};
|
||||
|
||||
#endif // CODERS_COMMONS_H_
|
||||
#endif // CODERS_COMMONS_HPP_
|
||||
@ -1,10 +1,11 @@
|
||||
#include "gzip.h"
|
||||
#include "gzip.hpp"
|
||||
|
||||
#include "byte_utils.hpp"
|
||||
|
||||
#define ZLIB_CONST
|
||||
#include <zlib.h>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include "byte_utils.h"
|
||||
|
||||
std::vector<ubyte> gzip::compress(const ubyte* src, size_t size) {
|
||||
size_t buffer_size = 23+size*1.01;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#ifndef CODERS_GZIP_H_
|
||||
#define CODERS_GZIP_H_
|
||||
#ifndef CODERS_GZIP_HPP_
|
||||
#define CODERS_GZIP_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
#include <vector>
|
||||
#include "../typedefs.h"
|
||||
|
||||
namespace gzip {
|
||||
const unsigned char MAGIC[] = "\x1F\x8B";
|
||||
@ -18,4 +18,4 @@ namespace gzip {
|
||||
std::vector<ubyte> decompress(const ubyte* src, size_t size);
|
||||
}
|
||||
|
||||
#endif // CODERS_GZIP_H_
|
||||
#endif // CODERS_GZIP_HPP_
|
||||
49
src/coders/imageio.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "imageio.hpp"
|
||||
|
||||
#include "png.hpp"
|
||||
#include "../graphics/core/ImageData.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using image_reader = std::function<std::unique_ptr<ImageData>(const std::string&)>;
|
||||
using image_writer = std::function<void(const std::string&, const ImageData*)>;
|
||||
|
||||
static std::unordered_map<std::string, image_reader> readers {
|
||||
{".png", png::load_image},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, image_writer> 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<ImageData> 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<ImageData>(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);
|
||||
}
|
||||
19
src/coders/imageio.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef CODERS_IMAGEIO_HPP_
|
||||
#define CODERS_IMAGEIO_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
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<ImageData> read(const std::string& filename);
|
||||
void write(const std::string& filename, const ImageData* image);
|
||||
}
|
||||
|
||||
#endif // CODERS_IMAGEIO_HPP_
|
||||
@ -1,19 +1,31 @@
|
||||
#include "json.h"
|
||||
#include "json.hpp"
|
||||
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../util/stringutil.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
|
||||
#include "commons.h"
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
using namespace json;
|
||||
using namespace dynamic;
|
||||
|
||||
inline void newline(std::stringstream& ss,
|
||||
bool nice, uint indent,
|
||||
const std::string& indentstr) {
|
||||
class Parser : BasicParser {
|
||||
std::unique_ptr<dynamic::List> parseList();
|
||||
std::unique_ptr<dynamic::Map> parseObject();
|
||||
dynamic::Value parseValue();
|
||||
public:
|
||||
Parser(std::string_view filename, std::string_view source);
|
||||
|
||||
std::unique_ptr<dynamic::Map> parse();
|
||||
};
|
||||
|
||||
inline void newline(
|
||||
std::stringstream& ss,
|
||||
bool nice, uint indent,
|
||||
const std::string& indentstr
|
||||
) {
|
||||
if (nice) {
|
||||
ss << "\n";
|
||||
for (uint i = 0; i < indent; i++) {
|
||||
@ -24,39 +36,37 @@ inline void newline(std::stringstream& ss,
|
||||
}
|
||||
}
|
||||
|
||||
void stringify(const Value* value,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice);
|
||||
void stringifyObj(
|
||||
const Map* obj,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice
|
||||
);
|
||||
|
||||
void stringifyObj(const Map* obj,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice);
|
||||
|
||||
void stringify(const Value* value,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice) {
|
||||
if (value->type == valtype::map) {
|
||||
stringifyObj(value->value.map, ss, indent, indentstr, nice);
|
||||
void stringifyValue(
|
||||
const Value& value,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice
|
||||
) {
|
||||
if (auto map = std::get_if<Map_sptr>(&value)) {
|
||||
stringifyObj(map->get(), ss, indent, indentstr, nice);
|
||||
}
|
||||
else if (value->type == valtype::list) {
|
||||
auto list = value->value.list;
|
||||
else if (auto listptr = std::get_if<List_sptr>(&value)) {
|
||||
auto list = *listptr;
|
||||
if (list->size() == 0) {
|
||||
ss << "[]";
|
||||
return;
|
||||
}
|
||||
ss << '[';
|
||||
for (uint i = 0; i < list->size(); i++) {
|
||||
Value* value = list->get(i);
|
||||
Value& value = list->get(i);
|
||||
if (i > 0 || nice) {
|
||||
newline(ss, nice, indent, indentstr);
|
||||
}
|
||||
stringify(value, ss, indent+1, indentstr, nice);
|
||||
stringifyValue(value, ss, indent+1, indentstr, nice);
|
||||
if (i + 1 < list->size()) {
|
||||
ss << ',';
|
||||
}
|
||||
@ -65,23 +75,26 @@ void stringify(const Value* value,
|
||||
newline(ss, true, indent - 1, indentstr);
|
||||
}
|
||||
ss << ']';
|
||||
} else if (value->type == valtype::boolean) {
|
||||
ss << (value->value.boolean ? "true" : "false");
|
||||
} else if (value->type == valtype::number) {
|
||||
ss << std::setprecision(15);
|
||||
ss << value->value.decimal;
|
||||
} else if (value->type == valtype::integer) {
|
||||
ss << value->value.integer;
|
||||
} else if (value->type == valtype::string) {
|
||||
ss << escape_string(*value->value.str);
|
||||
} else if (auto flag = std::get_if<bool>(&value)) {
|
||||
ss << (*flag ? "true" : "false");
|
||||
} else if (auto num = std::get_if<number_t>(&value)) {
|
||||
ss << std::setprecision(15) << *num;
|
||||
} else if (auto num = std::get_if<integer_t>(&value)) {
|
||||
ss << *num;
|
||||
} else if (auto str = std::get_if<std::string>(&value)) {
|
||||
ss << util::escape(*str);
|
||||
} else {
|
||||
ss << "null";
|
||||
}
|
||||
}
|
||||
|
||||
void stringifyObj(const Map* obj,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice) {
|
||||
void stringifyObj(
|
||||
const Map* obj,
|
||||
std::stringstream& ss,
|
||||
int indent,
|
||||
const std::string& indentstr,
|
||||
bool nice
|
||||
) {
|
||||
if (obj->values.empty()) {
|
||||
ss << "{}";
|
||||
return;
|
||||
@ -93,9 +106,9 @@ void stringifyObj(const Map* obj,
|
||||
if (index > 0 || nice) {
|
||||
newline(ss, nice, indent, indentstr);
|
||||
}
|
||||
Value* value = entry.second.get();
|
||||
ss << escape_string(key) << ": ";
|
||||
stringify(value, ss, indent+1, indentstr, nice);
|
||||
const Value& value = entry.second;
|
||||
ss << util::escape(key) << ": ";
|
||||
stringifyValue(value, ss, indent+1, indentstr, nice);
|
||||
index++;
|
||||
if (index < obj->values.size()) {
|
||||
ss << ',';
|
||||
@ -108,19 +121,30 @@ void stringifyObj(const Map* obj,
|
||||
}
|
||||
|
||||
std::string json::stringify(
|
||||
const Map* obj,
|
||||
bool nice,
|
||||
const std::string& indent) {
|
||||
const Map* obj,
|
||||
bool nice,
|
||||
const std::string& indent
|
||||
) {
|
||||
std::stringstream ss;
|
||||
stringifyObj(obj, ss, 1, indent, nice);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Parser::Parser(std::string filename, std::string source)
|
||||
: BasicParser(filename, source) {
|
||||
std::string json::stringify(
|
||||
const dynamic::Value& value,
|
||||
bool nice,
|
||||
const std::string& indent
|
||||
) {
|
||||
std::stringstream ss;
|
||||
stringifyValue(value, ss, 1, indent, nice);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Map* Parser::parse() {
|
||||
Parser::Parser(std::string_view filename, std::string_view source)
|
||||
: BasicParser(filename, source) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Map> Parser::parse() {
|
||||
char next = peek();
|
||||
if (next != '{') {
|
||||
throw error("'{' expected");
|
||||
@ -128,7 +152,7 @@ Map* Parser::parse() {
|
||||
return parseObject();
|
||||
}
|
||||
|
||||
Map* Parser::parseObject() {
|
||||
std::unique_ptr<Map> Parser::parseObject() {
|
||||
expect('{');
|
||||
auto obj = std::make_unique<Map>();
|
||||
auto& map = obj->values;
|
||||
@ -154,10 +178,10 @@ Map* Parser::parseObject() {
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
return obj.release();
|
||||
return obj;
|
||||
}
|
||||
|
||||
List* Parser::parseList() {
|
||||
std::unique_ptr<List> Parser::parseList() {
|
||||
expect('[');
|
||||
auto arr = std::make_unique<List>();
|
||||
auto& values = arr->values;
|
||||
@ -166,7 +190,7 @@ List* Parser::parseList() {
|
||||
skipLine();
|
||||
continue;
|
||||
}
|
||||
values.push_back(std::unique_ptr<Value>(parseValue()));
|
||||
values.push_back(parseValue());
|
||||
|
||||
char next = peek();
|
||||
if (next == ',') {
|
||||
@ -178,75 +202,49 @@ List* Parser::parseList() {
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
return arr.release();
|
||||
return arr;
|
||||
}
|
||||
|
||||
Value* Parser::parseValue() {
|
||||
Value Parser::parseValue() {
|
||||
char next = peek();
|
||||
dynamic::valvalue val;
|
||||
if (next == '-' || next == '+') {
|
||||
pos++;
|
||||
number_u num;
|
||||
valtype type;
|
||||
if (parseNumber(next == '-' ? -1 : 1, num)) {
|
||||
val.integer = num.ival;
|
||||
type = valtype::integer;
|
||||
} else {
|
||||
val.decimal = num.fval;
|
||||
type = valtype::number;
|
||||
}
|
||||
return new Value(type, val);
|
||||
return parseNumber(next == '-' ? -1 : 1);
|
||||
}
|
||||
if (is_identifier_start(next)) {
|
||||
std::string literal = parseName();
|
||||
if (literal == "true") {
|
||||
val.boolean = true;
|
||||
return new Value(valtype::boolean, val);
|
||||
return true;
|
||||
} else if (literal == "false") {
|
||||
val.boolean = false;
|
||||
return new Value(valtype::boolean, val);
|
||||
return false;
|
||||
} else if (literal == "inf") {
|
||||
val.decimal = INFINITY;
|
||||
return new Value(valtype::number, val);
|
||||
return INFINITY;
|
||||
} else if (literal == "nan") {
|
||||
val.decimal = NAN;
|
||||
return new Value(valtype::number, val);
|
||||
return NAN;
|
||||
}
|
||||
throw error("invalid literal ");
|
||||
}
|
||||
if (next == '{') {
|
||||
val.map = parseObject();
|
||||
return new Value(valtype::map, val);
|
||||
return Map_sptr(parseObject().release());
|
||||
}
|
||||
if (next == '[') {
|
||||
val.list = parseList();
|
||||
return new Value(valtype::list, val);
|
||||
return List_sptr(parseList().release());
|
||||
}
|
||||
if (is_digit(next)) {
|
||||
number_u num;
|
||||
valtype type;
|
||||
if (parseNumber(1, num)) {
|
||||
val.integer = num.ival;
|
||||
type = valtype::integer;
|
||||
} else {
|
||||
val.decimal = num.fval;
|
||||
type = valtype::number;
|
||||
}
|
||||
return new Value(type, val);
|
||||
return parseNumber(1);
|
||||
}
|
||||
if (next == '"' || next == '\'') {
|
||||
pos++;
|
||||
val.str = new std::string(parseString(next));
|
||||
return new Value(valtype::string, val);
|
||||
return parseString(next);
|
||||
}
|
||||
throw error("unexpected character '"+std::string({next})+"'");
|
||||
}
|
||||
|
||||
std::unique_ptr<Map> json::parse(std::string filename, std::string source) {
|
||||
std::unique_ptr<Map> json::parse(const std::string& filename, const std::string& source) {
|
||||
Parser parser(filename, source);
|
||||
return std::unique_ptr<Map>(parser.parse());
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
std::unique_ptr<Map> json::parse(std::string source) {
|
||||
std::unique_ptr<Map> json::parse(const std::string& source) {
|
||||
return parse("<string>", source);
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
#ifndef CODERS_JSON_H_
|
||||
#define CODERS_JSON_H_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "commons.h"
|
||||
#include "../typedefs.h"
|
||||
|
||||
#include "binary_json.h"
|
||||
|
||||
namespace dynamic {
|
||||
class Map;
|
||||
class List;
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace json {
|
||||
class Parser : public BasicParser {
|
||||
dynamic::List* parseList();
|
||||
dynamic::Map* parseObject();
|
||||
dynamic::Value* parseValue();
|
||||
public:
|
||||
Parser(std::string filename, std::string source);
|
||||
|
||||
dynamic::Map* parse();
|
||||
};
|
||||
|
||||
extern std::unique_ptr<dynamic::Map> parse(std::string filename, std::string source);
|
||||
extern std::unique_ptr<dynamic::Map> parse(std::string source);
|
||||
|
||||
extern std::string stringify(
|
||||
const dynamic::Map* obj,
|
||||
bool nice,
|
||||
const std::string& indent);
|
||||
}
|
||||
|
||||
#endif // CODERS_JSON_H_
|
||||
33
src/coders/json.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef CODERS_JSON_HPP_
|
||||
#define CODERS_JSON_HPP_
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "binary_json.hpp"
|
||||
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace json {
|
||||
std::unique_ptr<dynamic::Map> parse(const std::string& filename, const std::string& source);
|
||||
std::unique_ptr<dynamic::Map> parse(const std::string& source);
|
||||
|
||||
std::string stringify(
|
||||
const dynamic::Map* obj,
|
||||
bool nice,
|
||||
const std::string& indent
|
||||
);
|
||||
|
||||
std::string stringify(
|
||||
const dynamic::Value& value,
|
||||
bool nice,
|
||||
const std::string& indent
|
||||
);
|
||||
}
|
||||
|
||||
#endif // CODERS_JSON_HPP_
|
||||
@ -1,13 +1,16 @@
|
||||
#include "ogg.h"
|
||||
#include "ogg.hpp"
|
||||
|
||||
#include "../debug/Logger.hpp"
|
||||
#include "../audio/audio.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vorbis/codec.h>
|
||||
#include <vorbis/vorbisfile.h>
|
||||
|
||||
#include "../audio/audio.h"
|
||||
#include "../typedefs.h"
|
||||
static debug::Logger logger("ogg");
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace audio;
|
||||
|
||||
static inline std::string vorbis_error_message(int code) {
|
||||
@ -31,7 +34,7 @@ static inline std::string vorbis_error_message(int code) {
|
||||
}
|
||||
}
|
||||
|
||||
audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) {
|
||||
std::unique_ptr<audio::PCM> ogg::load_pcm(const fs::path& file, bool headerOnly) {
|
||||
OggVorbis_File vf;
|
||||
int code;
|
||||
if ((code = ov_fopen(file.u8string().c_str(), &vf))) {
|
||||
@ -56,7 +59,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);
|
||||
}
|
||||
@ -64,7 +67,9 @@ audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) {
|
||||
totalSamples = data.size() / channels / 2;
|
||||
}
|
||||
ov_clear(&vf);
|
||||
return new PCM(std::move(data), totalSamples, channels, 16, sampleRate, seekable);
|
||||
return std::make_unique<PCM>(
|
||||
std::move(data), totalSamples, channels, 16, sampleRate, seekable
|
||||
);
|
||||
}
|
||||
|
||||
class OggStream : public PCMStream {
|
||||
@ -98,7 +103,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;
|
||||
@ -147,11 +152,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
PCMStream* ogg::create_stream(const std::filesystem::path& file) {
|
||||
std::unique_ptr<PCMStream> ogg::create_stream(const fs::path& file) {
|
||||
OggVorbis_File vf;
|
||||
int code;
|
||||
if ((code = ov_fopen(file.u8string().c_str(), &vf))) {
|
||||
throw std::runtime_error("vorbis: "+vorbis_error_message(code));
|
||||
}
|
||||
return new OggStream(std::move(vf));
|
||||
return std::make_unique<OggStream>(std::move(vf));
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
#ifndef CODERS_OGG_H_
|
||||
#define CODERS_OGG_H_
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
class PCMStream;
|
||||
}
|
||||
|
||||
namespace ogg {
|
||||
extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly);
|
||||
extern audio::PCMStream* create_stream(const std::filesystem::path& file);
|
||||
}
|
||||
|
||||
#endif // CODERS_OGG_H_
|
||||
16
src/coders/ogg.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef CODERS_OGG_HPP_
|
||||
#define CODERS_OGG_HPP_
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
class PCMStream;
|
||||
}
|
||||
|
||||
namespace ogg {
|
||||
std::unique_ptr<audio::PCM> load_pcm(const std::filesystem::path& file, bool headerOnly);
|
||||
std::unique_ptr<audio::PCMStream> create_stream(const std::filesystem::path& file);
|
||||
}
|
||||
|
||||
#endif // CODERS_OGG_HPP_
|
||||
@ -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 <iostream>
|
||||
#include <memory>
|
||||
#include <GL/glew.h>
|
||||
|
||||
#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<png_byte[]> 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<png_byte[]>(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<ImageData> _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<ImageData>(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<ImageData> _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<ImageData>(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<ImageData> 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<ImageData> 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<Texture> 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);
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
#ifndef CODERS_PNG_H_
|
||||
#define CODERS_PNG_H_
|
||||
|
||||
#include <string>
|
||||
#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_ */
|
||||
16
src/coders/png.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef CODERS_PNG_HPP_
|
||||
#define CODERS_PNG_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Texture;
|
||||
class ImageData;
|
||||
|
||||
namespace png {
|
||||
std::unique_ptr<ImageData> load_image(const std::string& filename);
|
||||
void write_image(const std::string& filename, const ImageData* image);
|
||||
std::unique_ptr<Texture> load_texture(const std::string& filename);
|
||||
}
|
||||
|
||||
#endif // CODERS_PNG_HPP_
|
||||
90
src/coders/rle.cpp
Normal file
@ -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;
|
||||
}
|
||||
17
src/coders/rle.hpp
Normal file
@ -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_
|
||||
@ -1,249 +1,133 @@
|
||||
#include "toml.h"
|
||||
#include "commons.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <assert.h>
|
||||
|
||||
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<std::string>& 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();
|
||||
}
|
||||
}
|
||||
#include "toml.hpp"
|
||||
|
||||
#include "commons.hpp"
|
||||
#include "../data/setting.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../util/stringutil.hpp"
|
||||
#include "../files/settings_io.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace toml;
|
||||
|
||||
class Reader : 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)) {
|
||||
auto num = parseNumber(1);
|
||||
if (handler.has(name)) {
|
||||
handler.setValue(name, num);
|
||||
}
|
||||
} else if (c == '-' || c == '+') {
|
||||
int sign = c == '-' ? -1 : 1;
|
||||
pos++;
|
||||
auto num = parseNumber(sign);
|
||||
if (handler.has(name)) {
|
||||
handler.setValue(name, 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, flag);
|
||||
} else if (identifier == "inf") {
|
||||
handler.setValue(name, INFINITY);
|
||||
} else if (identifier == "nan") {
|
||||
handler.setValue(name, NAN);
|
||||
}
|
||||
}
|
||||
} else if (c == '"' || c == '\'') {
|
||||
pos++;
|
||||
std::string str = parseString(c);
|
||||
if (handler.has(name)) {
|
||||
handler.setValue(name, str);
|
||||
}
|
||||
} else {
|
||||
throw error("feature is not supported");
|
||||
}
|
||||
expectNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Reader(
|
||||
SettingsHandler& handler,
|
||||
std::string_view file,
|
||||
std::string_view 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<IntegerSetting*>(setting)) {
|
||||
ss << integer->get();
|
||||
} else if (auto number = dynamic_cast<NumberSetting*>(setting)) {
|
||||
ss << number->get();
|
||||
} else if (auto flag = dynamic_cast<FlagSetting*>(setting)) {
|
||||
ss << (flag->get() ? "true" : "false");
|
||||
} else if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
||||
ss << util::escape(string->get());
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
ss << "\n";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
#ifndef CODERS_TOML_H_
|
||||
#define CODERS_TOML_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
namespace toml {
|
||||
enum class fieldtype {
|
||||
ftbool,
|
||||
ftint,
|
||||
ftuint,
|
||||
ftfloat,
|
||||
ftstring,
|
||||
};
|
||||
|
||||
struct Field {
|
||||
fieldtype type;
|
||||
void* ptr;
|
||||
};
|
||||
|
||||
class Section {
|
||||
std::unordered_map<std::string, Field> fields;
|
||||
std::vector<std::string> 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<std::string>& keys() const;
|
||||
};
|
||||
|
||||
class Wrapper {
|
||||
std::unordered_map<std::string, Section*> sections;
|
||||
std::vector<std::string> 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_
|
||||
20
src/coders/toml.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef CODERS_TOML_HPP_
|
||||
#define CODERS_TOML_HPP_
|
||||
|
||||
#include "commons.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
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_
|
||||