merge main
This commit is contained in:
+40
-65
@@ -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_ */
|
||||
@@ -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_
|
||||
+113
-39
@@ -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");
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
@@ -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_
|
||||
+209
-203
@@ -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_
|
||||
+26
-21
@@ -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_
|
||||
+12
-10
@@ -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_
|
||||
+28
-30
@@ -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_
|
||||
+49
-69
@@ -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_
|
||||
+337
-344
@@ -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_
|
||||
+3
-2
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
+94
-96
@@ -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_
|
||||
@@ -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_
|
||||
+15
-10
@@ -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_
|
||||
@@ -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_
|
||||
+191
-204
@@ -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_ */
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
+133
-249
@@ -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_
|
||||
@@ -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_
|
||||
+17
-10
@@ -1,13 +1,17 @@
|
||||
#include "wav.h"
|
||||
#include "wav.hpp"
|
||||
|
||||
#include "../audio/audio.hpp"
|
||||
#include "../debug/Logger.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../audio/audio.h"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static debug::Logger logger("wav-coder");
|
||||
|
||||
bool is_big_endian() {
|
||||
uint32_t ui32_v = 0x01020304;
|
||||
@@ -65,7 +69,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
if (in.fail()) {
|
||||
std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl;
|
||||
logger.error() << "Wav::load_pcm: I/O error ocurred";
|
||||
return -1;
|
||||
}
|
||||
return in.gcount();
|
||||
@@ -114,7 +118,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
audio::PCMStream* wav::create_stream(const std::filesystem::path& file) {
|
||||
std::unique_ptr<audio::PCMStream> wav::create_stream(const fs::path& file) {
|
||||
std::ifstream in(file, std::ios::binary);
|
||||
if(!in.is_open()){
|
||||
throw std::runtime_error("could not to open file '"+file.u8string()+"'");
|
||||
@@ -197,7 +201,6 @@ audio::PCMStream* wav::create_stream(const std::filesystem::path& file) {
|
||||
}
|
||||
|
||||
if(std::strncmp(buffer, "data", 4) != 0){
|
||||
std::cerr << buffer << std::endl;
|
||||
throw std::runtime_error("file is not a valid WAVE file (doesn't have 'data' tag)");
|
||||
}
|
||||
|
||||
@@ -214,11 +217,13 @@ audio::PCMStream* wav::create_stream(const std::filesystem::path& file) {
|
||||
if(in.fail()){
|
||||
throw std::runtime_error("fail state set on the file");
|
||||
}
|
||||
return new WavStream(std::move(in), channels, bitsPerSample, sampleRate, size, initialOffset);
|
||||
return std::make_unique<WavStream>(
|
||||
std::move(in), channels, bitsPerSample, sampleRate, size, initialOffset
|
||||
);
|
||||
}
|
||||
|
||||
audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) {
|
||||
std::unique_ptr<audio::PCMStream> stream(wav::create_stream(file));
|
||||
std::unique_ptr<audio::PCM> wav::load_pcm(const fs::path& file, bool headerOnly) {
|
||||
auto stream = wav::create_stream(file);
|
||||
|
||||
size_t totalSamples = stream->getTotalSamples();
|
||||
uint channels = stream->getChannels();
|
||||
@@ -233,5 +238,7 @@ audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) {
|
||||
data.resize(size);
|
||||
stream->readFully(data.data(), size, false);
|
||||
}
|
||||
return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true);
|
||||
return std::make_unique<audio::PCM>(
|
||||
std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef CODERS_WAV_H_
|
||||
#define CODERS_WAV_H_
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
class PCMStream;
|
||||
}
|
||||
|
||||
namespace wav {
|
||||
extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly);
|
||||
extern audio::PCMStream* create_stream(const std::filesystem::path& file);
|
||||
}
|
||||
|
||||
#endif // CODERS_WAV_H_
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef CODERS_WAV_HPP_
|
||||
#define CODERS_WAV_HPP_
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
class PCMStream;
|
||||
}
|
||||
|
||||
namespace wav {
|
||||
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_WAV_HPP_
|
||||
+21
-15
@@ -1,9 +1,10 @@
|
||||
#include "xml.h"
|
||||
#include "xml.hpp"
|
||||
|
||||
#include "../util/stringutil.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include "../util/stringutil.h"
|
||||
|
||||
using namespace xml;
|
||||
|
||||
@@ -36,7 +37,7 @@ bool Attribute::asBool() const {
|
||||
glm::vec2 Attribute::asVec2() const {
|
||||
size_t pos = text.find(',');
|
||||
if (pos == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec2 value "+escape_string(text));
|
||||
return glm::vec2(util::parse_double(text, 0, text.length()));
|
||||
}
|
||||
return glm::vec2(
|
||||
util::parse_double(text, 0, pos),
|
||||
@@ -48,11 +49,11 @@ glm::vec2 Attribute::asVec2() const {
|
||||
glm::vec3 Attribute::asVec3() const {
|
||||
size_t pos1 = text.find(',');
|
||||
if (pos1 == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec3 value "+escape_string(text));
|
||||
return glm::vec3(util::parse_double(text, 0, text.length()));
|
||||
}
|
||||
size_t pos2 = text.find(',', pos1+1);
|
||||
if (pos2 == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec3 value "+escape_string(text));
|
||||
throw std::runtime_error("invalid vec3 value "+util::quote(text));
|
||||
}
|
||||
return glm::vec3(
|
||||
util::parse_double(text, 0, pos1),
|
||||
@@ -65,15 +66,15 @@ glm::vec3 Attribute::asVec3() const {
|
||||
glm::vec4 Attribute::asVec4() const {
|
||||
size_t pos1 = text.find(',');
|
||||
if (pos1 == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec4 value "+escape_string(text));
|
||||
return glm::vec4(util::parse_double(text, 0, text.length()));
|
||||
}
|
||||
size_t pos2 = text.find(',', pos1+1);
|
||||
if (pos2 == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec4 value "+escape_string(text));
|
||||
throw std::runtime_error("invalid vec4 value "+util::quote(text));
|
||||
}
|
||||
size_t pos3 = text.find(',', pos2+1);
|
||||
if (pos3 == std::string::npos) {
|
||||
throw std::runtime_error("invalid vec4 value "+escape_string(text));
|
||||
throw std::runtime_error("invalid vec4 value "+util::quote(text));
|
||||
}
|
||||
return glm::vec4(
|
||||
util::parse_double(text, 0, pos1),
|
||||
@@ -99,7 +100,7 @@ glm::vec4 Attribute::asColor() const {
|
||||
}
|
||||
return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f);
|
||||
} else {
|
||||
throw std::runtime_error("hex colors are only supported");
|
||||
return asVec4() / 255.f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +177,7 @@ const std::string& Document::getEncoding() const {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
Parser::Parser(std::string filename, std::string source)
|
||||
Parser::Parser(std::string_view filename, std::string_view source)
|
||||
: BasicParser(filename, source) {
|
||||
}
|
||||
|
||||
@@ -196,8 +197,13 @@ xmlelement Parser::parseOpenTag() {
|
||||
if (peek() == '=') {
|
||||
nextChar();
|
||||
skipWhitespace();
|
||||
expect('"');
|
||||
attrtext = parseString('"');
|
||||
|
||||
char quote = peek();
|
||||
if (quote != '\'' && quote != '"') {
|
||||
throw error("string literal expected");
|
||||
}
|
||||
skip(1);
|
||||
attrtext = parseString(quote);
|
||||
}
|
||||
node->set(attrname, attrtext);
|
||||
}
|
||||
@@ -244,7 +250,7 @@ std::string Parser::parseText() {
|
||||
}
|
||||
nextChar();
|
||||
}
|
||||
return source.substr(start, pos-start);
|
||||
return std::string(source.substr(start, pos-start));
|
||||
}
|
||||
|
||||
inline bool is_xml_identifier_start(char c) {
|
||||
@@ -264,7 +270,7 @@ std::string Parser::parseXMLName() {
|
||||
while (hasNext() && is_xml_identifier_part(source[pos])) {
|
||||
pos++;
|
||||
}
|
||||
return source.substr(start, pos-start);
|
||||
return std::string(source.substr(start, pos-start));
|
||||
}
|
||||
|
||||
xmlelement Parser::parseElement() {
|
||||
@@ -377,7 +383,7 @@ static void stringifyElement(
|
||||
auto attr = entry.second;
|
||||
ss << attr.getName();
|
||||
if (!attr.getText().empty()) {
|
||||
ss << "=" << escape_string(attr.getText());
|
||||
ss << "=" << util::escape(attr.getText());
|
||||
}
|
||||
if (count + 1 < int(attrs.size())) {
|
||||
ss << " ";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#ifndef CODERS_XML_H_
|
||||
#define CODERS_XML_H_
|
||||
#ifndef CODERS_XML_HPP_
|
||||
#define CODERS_XML_HPP_
|
||||
|
||||
#include "commons.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -7,8 +9,6 @@
|
||||
#include <glm/glm.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
namespace xml {
|
||||
class Node;
|
||||
class Attribute;
|
||||
@@ -108,7 +108,7 @@ namespace xml {
|
||||
const std::string& getEncoding() const;
|
||||
};
|
||||
|
||||
class Parser : public BasicParser {
|
||||
class Parser : BasicParser {
|
||||
xmldocument document;
|
||||
|
||||
xmlelement parseOpenTag();
|
||||
@@ -118,7 +118,7 @@ namespace xml {
|
||||
std::string parseText();
|
||||
std::string parseXMLName();
|
||||
public:
|
||||
Parser(std::string filename, std::string source);
|
||||
Parser(std::string_view filename, std::string_view source);
|
||||
|
||||
xmldocument parse();
|
||||
};
|
||||
@@ -141,4 +141,4 @@ namespace xml {
|
||||
extern xmldocument parse(std::string filename, std::string source);
|
||||
}
|
||||
|
||||
#endif // CODERS_XML_H_
|
||||
#endif // CODERS_XML_HPP_
|
||||
@@ -1,17 +1,24 @@
|
||||
#ifndef SRC_CONSTANTS_H_
|
||||
#define SRC_CONSTANTS_H_
|
||||
#ifndef CONSTANTS_HPP_
|
||||
#define CONSTANTS_HPP_
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include "typedefs.h"
|
||||
|
||||
inline constexpr int ENGINE_VERSION_MAJOR = 0;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 21;
|
||||
inline constexpr bool ENGINE_VERSION_INDEV = true;
|
||||
|
||||
#ifdef NDEBUG
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
#else
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||
#endif // NDEBUG
|
||||
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.21";
|
||||
|
||||
inline constexpr int BLOCK_AIR = 0;
|
||||
inline constexpr int ITEM_EMPTY = 0;
|
||||
inline constexpr blockid_t BLOCK_AIR = 0;
|
||||
inline constexpr itemid_t ITEM_EMPTY = 0;
|
||||
|
||||
inline constexpr int CHUNK_W = 16;
|
||||
inline constexpr int CHUNK_H = 256;
|
||||
@@ -20,19 +27,21 @@ inline constexpr int CHUNK_D = 16;
|
||||
inline constexpr uint VOXEL_USER_BITS = 8;
|
||||
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;
|
||||
|
||||
/// @brief pixel size of an item inventory icon
|
||||
inline constexpr int ITEM_ICON_SIZE = 48;
|
||||
|
||||
/* Chunk volume (count of voxels per Chunk) */
|
||||
/// @brief chunk volume (count of voxels per Chunk)
|
||||
inline constexpr int CHUNK_VOL = (CHUNK_W * CHUNK_H * CHUNK_D);
|
||||
|
||||
/* BLOCK_VOID is block id used to mark non-existing voxel (voxel of missing chunk) */
|
||||
/// @brief block id used to mark non-existing voxel (voxel of missing chunk)
|
||||
inline constexpr blockid_t BLOCK_VOID = std::numeric_limits<blockid_t>::max();
|
||||
/// @brief item id used to mark non-existing item (error)
|
||||
inline constexpr itemid_t ITEM_VOID = std::numeric_limits<itemid_t>::max();
|
||||
|
||||
/// @brief max number of block definitions possible
|
||||
inline constexpr blockid_t MAX_BLOCKS = BLOCK_VOID;
|
||||
|
||||
inline constexpr uint vox_index(uint x, uint y, uint z, uint w=CHUNK_W, uint d=CHUNK_D) {
|
||||
return (y * d + z) * w + x;
|
||||
return (y * d + z) * w + x;
|
||||
}
|
||||
|
||||
inline const std::string SHADERS_FOLDER = "shaders";
|
||||
@@ -41,4 +50,4 @@ inline const std::string FONTS_FOLDER = "fonts";
|
||||
inline const std::string LAYOUTS_FOLDER = "layouts";
|
||||
inline const std::string SOUNDS_FOLDER = "sounds";
|
||||
|
||||
#endif // SRC_CONSTANTS_H_
|
||||
#endif // CONSTANTS_HPP_
|
||||
+14
-9
@@ -1,14 +1,14 @@
|
||||
#include "Content.h"
|
||||
#include "Content.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "../voxels/Block.h"
|
||||
#include "../items/ItemDef.h"
|
||||
#include "../voxels/Block.hpp"
|
||||
#include "../items/ItemDef.hpp"
|
||||
|
||||
#include "ContentPack.h"
|
||||
#include "../logic/scripting/scripting.h"
|
||||
#include "ContentPack.hpp"
|
||||
#include "../logic/scripting/scripting.hpp"
|
||||
|
||||
ContentBuilder::~ContentBuilder() {}
|
||||
|
||||
@@ -95,9 +95,7 @@ Content* ContentBuilder::build() {
|
||||
}
|
||||
|
||||
blockDefsIndices.push_back(def);
|
||||
if (groups->find(def->drawGroup) == groups->end()) {
|
||||
groups->insert(def->drawGroup);
|
||||
}
|
||||
groups->insert(def->drawGroup);
|
||||
}
|
||||
|
||||
std::vector<ItemDef*> itemDefsIndices;
|
||||
@@ -155,7 +153,14 @@ Content::Content(
|
||||
drawGroups(std::move(drawGroups))
|
||||
{}
|
||||
|
||||
Content::~Content() {}
|
||||
Content::~Content() {
|
||||
for (auto& entry : blockDefs) {
|
||||
delete entry.second;
|
||||
}
|
||||
for (auto& entry : itemDefs) {
|
||||
delete entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
Block* Content::findBlock(std::string id) const {
|
||||
auto found = blockDefs.find(id);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef CONTENT_CONTENT_H_
|
||||
#define CONTENT_CONTENT_H_
|
||||
#ifndef CONTENT_CONTENT_HPP_
|
||||
#define CONTENT_CONTENT_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include "../typedefs.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../typedefs.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
|
||||
using DrawGroups = std::set<ubyte>;
|
||||
|
||||
@@ -146,4 +146,4 @@ public:
|
||||
const std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>>& getPacks() const;
|
||||
};
|
||||
|
||||
#endif // CONTENT_CONTENT_H_
|
||||
#endif // CONTENT_CONTENT_HPP_
|
||||
+15
-14
@@ -1,16 +1,15 @@
|
||||
#include "ContentLUT.h"
|
||||
#include "ContentLUT.hpp"
|
||||
|
||||
#include "Content.hpp"
|
||||
#include "../constants.hpp"
|
||||
#include "../files/files.hpp"
|
||||
#include "../coders/json.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
#include "../items/ItemDef.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Content.h"
|
||||
#include "../constants.h"
|
||||
#include "../files/files.h"
|
||||
#include "../coders/json.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../items/ItemDef.h"
|
||||
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
ContentLUT::ContentLUT(const Content* content, size_t blocksCount, size_t itemsCount) {
|
||||
auto* indices = content->getIndices();
|
||||
for (size_t i = 0; i < blocksCount; i++) {
|
||||
@@ -34,8 +33,10 @@ ContentLUT::ContentLUT(const Content* content, size_t blocksCount, size_t itemsC
|
||||
}
|
||||
}
|
||||
|
||||
ContentLUT* ContentLUT::create(const fs::path& filename,
|
||||
const Content* content) {
|
||||
std::shared_ptr<ContentLUT> ContentLUT::create(
|
||||
const fs::path& filename,
|
||||
const Content* content
|
||||
) {
|
||||
auto root = files::read_json(filename);
|
||||
auto blocklist = root->list("blocks");
|
||||
auto itemlist = root->list("items");
|
||||
@@ -48,7 +49,7 @@ ContentLUT* ContentLUT::create(const fs::path& filename,
|
||||
? std::max(itemlist->size(), indices->countItemDefs())
|
||||
: indices->countItemDefs();
|
||||
|
||||
auto lut = std::make_unique<ContentLUT>(content, blocks_c, items_c);
|
||||
auto lut = std::make_shared<ContentLUT>(content, blocks_c, items_c);
|
||||
|
||||
if (blocklist) {
|
||||
for (size_t i = 0; i < blocklist->size(); i++) {
|
||||
@@ -75,7 +76,7 @@ ContentLUT* ContentLUT::create(const fs::path& filename,
|
||||
}
|
||||
|
||||
if (lut->hasContentReorder() || lut->hasMissingContent()) {
|
||||
return lut.release();
|
||||
return lut;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#ifndef CONTENT_CONTENT_LUT_H_
|
||||
#define CONTENT_CONTENT_LUT_H_
|
||||
#ifndef CONTENT_CONTENT_LUT_HPP_
|
||||
#define CONTENT_CONTENT_LUT_HPP_
|
||||
|
||||
#include "Content.hpp"
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
#include "../constants.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../typedefs.h"
|
||||
#include "../constants.h"
|
||||
|
||||
#include "Content.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct contententry {
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static ContentLUT* create(
|
||||
static std::shared_ptr<ContentLUT> create(
|
||||
const fs::path& filename,
|
||||
const Content* content
|
||||
);
|
||||
@@ -93,4 +93,4 @@ public:
|
||||
std::vector<contententry> getMissingContent() const;
|
||||
};
|
||||
|
||||
#endif // CONTENT_CONTENT_LUT_H_
|
||||
#endif // CONTENT_CONTENT_LUT_HPP_
|
||||
@@ -1,4 +1,17 @@
|
||||
#include "ContentLoader.h"
|
||||
#include "ContentLoader.hpp"
|
||||
|
||||
#include "Content.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
#include "../coders/json.hpp"
|
||||
#include "../core_defs.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../debug/Logger.hpp"
|
||||
#include "../files/files.hpp"
|
||||
#include "../items/ItemDef.hpp"
|
||||
#include "../logic/scripting/scripting.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
#include "../util/listutil.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -6,21 +19,10 @@
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "Content.h"
|
||||
#include "../items/ItemDef.h"
|
||||
#include "../util/listutil.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../files/files.h"
|
||||
#include "../coders/json.h"
|
||||
#include "../typedefs.h"
|
||||
#include "../core_defs.h"
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
#include "ContentPack.h"
|
||||
#include "../logic/scripting/scripting.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static debug::Logger logger("content-loader");
|
||||
|
||||
ContentLoader::ContentLoader(ContentPack* pack) : pack(pack) {
|
||||
}
|
||||
|
||||
@@ -86,11 +88,11 @@ void ContentLoader::fixPackIndices() {
|
||||
auto blocksFolder = folder/ContentPack::BLOCKS_FOLDER;
|
||||
auto itemsFolder = folder/ContentPack::ITEMS_FOLDER;
|
||||
|
||||
std::unique_ptr<dynamic::Map> root;
|
||||
dynamic::Map_sptr root;
|
||||
if (fs::is_regular_file(indexFile)) {
|
||||
root = files::read_json(indexFile);
|
||||
} else {
|
||||
root.reset(new dynamic::Map());
|
||||
root = dynamic::create_map();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
@@ -100,7 +102,6 @@ void ContentLoader::fixPackIndices() {
|
||||
|
||||
if (modified){
|
||||
// rewrite modified json
|
||||
std::cout << indexFile << std::endl;
|
||||
files::write_json(indexFile, root.get());
|
||||
}
|
||||
}
|
||||
@@ -227,15 +228,15 @@ void ContentLoader::loadCustomBlockModel(Block& def, dynamic::Map* primitives) {
|
||||
def.modelBoxes.push_back(modelbox);
|
||||
|
||||
if (boxarr->size() == 7)
|
||||
for (uint i = 6; i < 12; i++) {
|
||||
for (uint j = 6; j < 12; j++) {
|
||||
def.modelTextures.push_back(boxarr->str(6));
|
||||
}
|
||||
else if (boxarr->size() == 12)
|
||||
for (uint i = 6; i < 12; i++) {
|
||||
def.modelTextures.push_back(boxarr->str(i));
|
||||
for (uint j = 6; j < 12; j++) {
|
||||
def.modelTextures.push_back(boxarr->str(j));
|
||||
}
|
||||
else
|
||||
for (uint i = 6; i < 12; i++) {
|
||||
for (uint j = 6; j < 12; j++) {
|
||||
def.modelTextures.push_back("notfound");
|
||||
}
|
||||
}
|
||||
@@ -321,11 +322,11 @@ BlockMaterial ContentLoader::loadBlockMaterial(fs::path file, std::string full)
|
||||
}
|
||||
|
||||
void ContentLoader::load(ContentBuilder& builder) {
|
||||
std::cout << "-- loading pack [" << pack->id << "]" << std::endl;
|
||||
logger.info() << "loading pack [" << pack->id << "]";
|
||||
|
||||
auto runtime = new ContentPackRuntime(*pack, scripting::create_pack_environment(*pack));
|
||||
builder.add(runtime);
|
||||
env = runtime->getEnvironment()->getId();
|
||||
env = runtime->getEnvironment();
|
||||
ContentPackStats& stats = runtime->getStatsWriteable();
|
||||
|
||||
fixPackIndices();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef CONTENT_CONTENT_LOADER_H_
|
||||
#define CONTENT_CONTENT_LOADER_H_
|
||||
#ifndef CONTENT_CONTENT_LOADER_HPP_
|
||||
#define CONTENT_CONTENT_LOADER_HPP_
|
||||
|
||||
#include "../voxels/Block.h"
|
||||
#include "../voxels/Block.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
@@ -18,7 +18,7 @@ namespace dynamic {
|
||||
|
||||
class ContentLoader {
|
||||
const ContentPack* pack;
|
||||
int env = 0;
|
||||
scriptenv env;
|
||||
|
||||
void loadBlock(Block& def, std::string full, std::string name);
|
||||
void loadCustomBlockModel(Block& def, dynamic::Map* primitives);
|
||||
@@ -38,4 +38,4 @@ public:
|
||||
void load(ContentBuilder& builder);
|
||||
};
|
||||
|
||||
#endif // CONTENT_CONTENT_LOADER_H_
|
||||
#endif // CONTENT_CONTENT_LOADER_HPP_
|
||||
+12
-37
@@ -1,13 +1,12 @@
|
||||
#include "ContentPack.h"
|
||||
#include "ContentPack.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../coders/json.h"
|
||||
#include "../files/files.h"
|
||||
#include "../files/engine_paths.h"
|
||||
#include "../data/dynamic.h"
|
||||
#include "../logic/scripting/scripting.h"
|
||||
#include "../coders/json.hpp"
|
||||
#include "../files/files.hpp"
|
||||
#include "../files/engine_paths.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -75,7 +74,9 @@ ContentPack ContentPack::read(fs::path folder) {
|
||||
auto dependencies = root->list("dependencies");
|
||||
if (dependencies) {
|
||||
for (size_t i = 0; i < dependencies->size(); i++) {
|
||||
pack.dependencies.push_back(dependencies->str(i));
|
||||
pack.dependencies.push_back(
|
||||
{DependencyLevel::required, dependencies->str(i)}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,21 +112,6 @@ void ContentPack::scanFolder(
|
||||
}
|
||||
}
|
||||
|
||||
void ContentPack::scan(
|
||||
fs::path rootfolder,
|
||||
EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs
|
||||
) {
|
||||
scanFolder(paths->getResources()/fs::path("content"), packs);
|
||||
scanFolder(paths->getUserfiles()/fs::path("content"), packs);
|
||||
scanFolder(rootfolder, packs);
|
||||
}
|
||||
|
||||
void ContentPack::scan(EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs) {
|
||||
scan(paths->getWorldFolder()/fs::path("content"), paths, packs);
|
||||
}
|
||||
|
||||
std::vector<std::string> ContentPack::worldPacksList(fs::path folder) {
|
||||
fs::path listfile = folder / fs::path("packs.list");
|
||||
if (!fs::is_regular_file(listfile)) {
|
||||
@@ -152,23 +138,12 @@ fs::path ContentPack::findPack(const EnginePaths* paths, fs::path worldDir, std:
|
||||
return folder;
|
||||
}
|
||||
|
||||
void ContentPack::readPacks(const EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs,
|
||||
const std::vector<std::string>& packnames,
|
||||
fs::path worldDir) {
|
||||
for (const auto& name : packnames) {
|
||||
fs::path packfolder = ContentPack::findPack(paths, worldDir, name);
|
||||
if (!fs::is_directory(packfolder)) {
|
||||
throw contentpack_error(name, packfolder,
|
||||
"could not to find pack '"+name+"'");
|
||||
}
|
||||
packs.push_back(ContentPack::read(packfolder));
|
||||
}
|
||||
}
|
||||
|
||||
ContentPackRuntime::ContentPackRuntime(
|
||||
ContentPack info,
|
||||
std::unique_ptr<scripting::Environment> env
|
||||
scriptenv env
|
||||
) : info(info), env(std::move(env))
|
||||
{
|
||||
}
|
||||
|
||||
ContentPackRuntime::~ContentPackRuntime() {
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#ifndef CONTENT_CONTENT_PACK_H_
|
||||
#define CONTENT_CONTENT_PACK_H_
|
||||
#ifndef CONTENT_CONTENT_PACK_HPP_
|
||||
#define CONTENT_CONTENT_PACK_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -26,6 +28,19 @@ public:
|
||||
fs::path getFolder() const;
|
||||
};
|
||||
|
||||
enum class DependencyLevel {
|
||||
required, // dependency must be installed
|
||||
optional, // dependency will be installed if found
|
||||
weak, // dependency will not be installed automatically
|
||||
};
|
||||
|
||||
|
||||
/// @brief Content-pack that should be installed before the dependent
|
||||
struct DependencyPack {
|
||||
DependencyLevel level;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
struct ContentPack {
|
||||
std::string id = "none";
|
||||
std::string title = "untitled";
|
||||
@@ -33,7 +48,7 @@ struct ContentPack {
|
||||
std::string creator = "";
|
||||
std::string description = "no description";
|
||||
fs::path folder;
|
||||
std::vector<std::string> dependencies;
|
||||
std::vector<DependencyPack> dependencies;
|
||||
|
||||
fs::path getContentFile() const;
|
||||
|
||||
@@ -50,16 +65,6 @@ struct ContentPack {
|
||||
fs::path folder,
|
||||
std::vector<ContentPack>& packs
|
||||
);
|
||||
|
||||
static void scan(
|
||||
fs::path folder,
|
||||
EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs
|
||||
);
|
||||
static void scan(
|
||||
EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs
|
||||
);
|
||||
|
||||
static std::vector<std::string> worldPacksList(fs::path folder);
|
||||
|
||||
@@ -68,13 +73,6 @@ struct ContentPack {
|
||||
fs::path worldDir,
|
||||
std::string name
|
||||
);
|
||||
|
||||
static void readPacks(
|
||||
const EnginePaths* paths,
|
||||
std::vector<ContentPack>& packs,
|
||||
const std::vector<std::string>& names,
|
||||
fs::path worldDir
|
||||
);
|
||||
};
|
||||
|
||||
struct ContentPackStats {
|
||||
@@ -89,12 +87,13 @@ struct ContentPackStats {
|
||||
class ContentPackRuntime {
|
||||
ContentPack info;
|
||||
ContentPackStats stats {};
|
||||
std::unique_ptr<scripting::Environment> env;
|
||||
scriptenv env;
|
||||
public:
|
||||
ContentPackRuntime(
|
||||
ContentPack info,
|
||||
std::unique_ptr<scripting::Environment> env
|
||||
scriptenv env
|
||||
);
|
||||
~ContentPackRuntime();
|
||||
|
||||
inline const ContentPackStats& getStats() const {
|
||||
return stats;
|
||||
@@ -112,9 +111,9 @@ public:
|
||||
return info;
|
||||
}
|
||||
|
||||
inline scripting::Environment* getEnvironment() const {
|
||||
return env.get();
|
||||
inline scriptenv getEnvironment() const {
|
||||
return env;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTENT_CONTENT_PACK_H_
|
||||
#endif // CONTENT_CONTENT_PACK_HPP_
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "PacksManager.hpp"
|
||||
|
||||
#include "../util/listutil.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
|
||||
PacksManager::PacksManager() {
|
||||
}
|
||||
|
||||
void PacksManager::setSources(std::vector<fs::path> sources) {
|
||||
this->sources = sources;
|
||||
}
|
||||
|
||||
void PacksManager::scan() {
|
||||
packs.clear();
|
||||
|
||||
std::vector<ContentPack> packsList;
|
||||
for (auto& folder : sources) {
|
||||
ContentPack::scanFolder(folder, packsList);
|
||||
for (auto& pack : packsList) {
|
||||
packs.emplace(pack.id, pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PacksManager::exclude(const std::string& id) {
|
||||
packs.erase(id);
|
||||
}
|
||||
|
||||
std::vector<std::string> PacksManager::getAllNames() const {
|
||||
std::vector<std::string> names;
|
||||
for (auto& entry : packs) {
|
||||
names.push_back(entry.first);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
std::vector<ContentPack> PacksManager::getAll(const std::vector<std::string>& names) const {
|
||||
std::vector<ContentPack> packsList;
|
||||
for (auto& name : names) {
|
||||
auto found = packs.find(name);
|
||||
if (found == packs.end()) {
|
||||
throw contentpack_error(name, fs::path(""), "pack not found");
|
||||
}
|
||||
packsList.push_back(found->second);
|
||||
}
|
||||
return packsList;
|
||||
}
|
||||
|
||||
static contentpack_error on_circular_dependency(std::queue<const ContentPack*>& queue) {
|
||||
const ContentPack* lastPack = queue.back();
|
||||
// circular dependency
|
||||
std::stringstream ss;
|
||||
ss << "circular dependency: " << lastPack->id;
|
||||
while (!queue.empty()) {
|
||||
auto* pack = queue.front();
|
||||
queue.pop();
|
||||
ss << " <- " << pack->id;
|
||||
}
|
||||
return contentpack_error(lastPack->id, lastPack->folder, ss.str());
|
||||
}
|
||||
|
||||
/// @brief Resolve pack dependencies
|
||||
/// @param pack current pack
|
||||
/// @param packs all available packs repository
|
||||
/// @param allNames all already done or enqueued packs
|
||||
/// @param added packs with all dependencies resolved
|
||||
/// @param queue current pass queue
|
||||
/// @param resolveWeaks make weak dependencies resolved if found but not added to queue
|
||||
/// @return true if all dependencies are already added or not found (optional/weak)
|
||||
/// @throws contentpack_error if required dependency is not found
|
||||
static bool resolve_dependencies (
|
||||
const ContentPack* pack,
|
||||
const std::unordered_map<std::string, ContentPack>& packs,
|
||||
std::vector<std::string>& allNames,
|
||||
const std::vector<std::string>& added,
|
||||
std::queue<const ContentPack*>& queue,
|
||||
bool resolveWeaks
|
||||
) {
|
||||
bool satisfied = true;
|
||||
for (auto& dep : pack->dependencies) {
|
||||
if (util::contains(added, dep.id)) {
|
||||
continue;
|
||||
}
|
||||
auto found = packs.find(dep.id);
|
||||
bool exists = found != packs.end();
|
||||
if (!exists && dep.level == DependencyLevel::required) {
|
||||
throw contentpack_error(dep.id, fs::path(), "dependency of '"+pack->id+"'");
|
||||
}
|
||||
if (!exists) {
|
||||
// ignored for optional or weak dependencies
|
||||
continue;
|
||||
}
|
||||
if (resolveWeaks && dep.level == DependencyLevel::weak) {
|
||||
// dependency pack is found but not added yet
|
||||
// resolveWeaks is used on second iteration, so it's will not be added
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!util::contains(allNames, dep.id) && dep.level != DependencyLevel::weak) {
|
||||
allNames.push_back(dep.id);
|
||||
queue.push(&found->second);
|
||||
}
|
||||
satisfied = false;
|
||||
}
|
||||
return satisfied;
|
||||
}
|
||||
|
||||
std::vector<std::string> PacksManager::assembly(const std::vector<std::string>& names) const {
|
||||
std::vector<std::string> allNames = names;
|
||||
std::vector<std::string> added;
|
||||
std::queue<const ContentPack*> queue;
|
||||
std::queue<const ContentPack*> queue2;
|
||||
|
||||
for (auto& name : names) {
|
||||
auto found = packs.find(name);
|
||||
if (found == packs.end()) {
|
||||
throw contentpack_error(name, fs::path(""), "pack not found");
|
||||
}
|
||||
queue.push(&found->second);
|
||||
}
|
||||
|
||||
bool resolveWeaks = false;
|
||||
while (!queue.empty()) {
|
||||
int addedInIteration = 0;
|
||||
while (!queue.empty()) {
|
||||
auto* pack = queue.front();
|
||||
queue.pop();
|
||||
|
||||
if (resolve_dependencies(pack, packs, allNames, added, queue, resolveWeaks)) {
|
||||
if (util::contains(added, pack->id)) {
|
||||
throw contentpack_error(pack->id, pack->folder, "pack duplication");
|
||||
}
|
||||
added.push_back(pack->id);
|
||||
addedInIteration++;
|
||||
} else {
|
||||
queue2.push(pack);
|
||||
}
|
||||
}
|
||||
std::swap(queue, queue2);
|
||||
|
||||
// nothing done but deferring
|
||||
if (addedInIteration == 0 && resolveWeaks) {
|
||||
throw on_circular_dependency(queue);
|
||||
}
|
||||
resolveWeaks = true;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
std::vector<std::string> PacksManager::getNames(const std::vector<ContentPack>& packs) {
|
||||
std::vector<std::string> result;
|
||||
for (const auto& pack : packs) {
|
||||
result.push_back(pack.id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef CONTENT_PACKS_MANAGER_HPP_
|
||||
#define CONTENT_PACKS_MANAGER_HPP_
|
||||
|
||||
#include "ContentPack.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class PacksManager {
|
||||
std::unordered_map<std::string, ContentPack> packs;
|
||||
std::vector<fs::path> sources;
|
||||
public:
|
||||
PacksManager();
|
||||
|
||||
/// @brief Set content packs sources (search folders)
|
||||
void setSources(std::vector<fs::path> sources);
|
||||
|
||||
/// @brief Scan sources and collect all found packs excluding duplication.
|
||||
/// Scanning order depends on sources order
|
||||
void scan();
|
||||
|
||||
/// @brief Remove pack from manager to make it invisible for assembly(...)
|
||||
void exclude(const std::string& id);
|
||||
|
||||
/// @brief Get all found packs
|
||||
std::vector<std::string> getAllNames() const;
|
||||
|
||||
/// @brief Get packs by names (id)
|
||||
/// @param names pack names
|
||||
/// @throws contentpack_error if pack not found
|
||||
std::vector<ContentPack> getAll(const std::vector<std::string>& names) const;
|
||||
|
||||
/// @brief Resolve all dependencies and fix packs order
|
||||
/// @param names required packs (method can add extra packs)
|
||||
/// @return resulting ordered vector of pack names
|
||||
/// @throws contentpack_error if required dependency not found or
|
||||
/// circular dependency detected
|
||||
std::vector<std::string> assembly(const std::vector<std::string>& names) const;
|
||||
|
||||
/// @brief Collect all pack names (identifiers) into a new vector
|
||||
static std::vector<std::string> getNames(const std::vector<ContentPack>& packs);
|
||||
};
|
||||
|
||||
#endif // CONTENT_PACKS_MANAGER_HPP_
|
||||
+44
-43
@@ -1,43 +1,44 @@
|
||||
#include "core_defs.h"
|
||||
|
||||
#include "items/ItemDef.h"
|
||||
#include "content/Content.h"
|
||||
#include "window/Window.h"
|
||||
#include "window/Events.h"
|
||||
#include "window/input.h"
|
||||
#include "voxels/Block.h"
|
||||
|
||||
// All in-game definitions (blocks, items, etc..)
|
||||
void corecontent::setup(ContentBuilder* builder) {
|
||||
Block& block = builder->createBlock("core:air");
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
block.skyLightPassing = true;
|
||||
block.obstacle = false;
|
||||
block.selectable = false;
|
||||
block.model = BlockModel::none;
|
||||
block.pickingItem = "core:empty";
|
||||
|
||||
ItemDef& item = builder->createItem("core:empty");
|
||||
item.iconType = item_icon_type::none;
|
||||
}
|
||||
|
||||
void corecontent::setup_bindings() {
|
||||
Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W);
|
||||
Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S);
|
||||
Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D);
|
||||
Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A);
|
||||
Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE);
|
||||
Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL);
|
||||
Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT);
|
||||
Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R);
|
||||
Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C);
|
||||
Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4);
|
||||
Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N);
|
||||
Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F);
|
||||
Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1);
|
||||
Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2);
|
||||
Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3);
|
||||
Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB);
|
||||
}
|
||||
#include "core_defs.hpp"
|
||||
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
|
||||
// All in-game definitions (blocks, items, etc..)
|
||||
void corecontent::setup(ContentBuilder* builder) {
|
||||
Block& block = builder->createBlock("core:air");
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
block.skyLightPassing = true;
|
||||
block.obstacle = false;
|
||||
block.selectable = false;
|
||||
block.model = BlockModel::none;
|
||||
block.pickingItem = "core:empty";
|
||||
|
||||
ItemDef& item = builder->createItem("core:empty");
|
||||
item.iconType = item_icon_type::none;
|
||||
}
|
||||
|
||||
void corecontent::setup_bindings() {
|
||||
Events::bind(BIND_DEVTOOLS_CONSOLE, inputtype::keyboard, keycode::GRAVE_ACCENT);
|
||||
Events::bind(BIND_MOVE_FORWARD, inputtype::keyboard, keycode::W);
|
||||
Events::bind(BIND_MOVE_BACK, inputtype::keyboard, keycode::S);
|
||||
Events::bind(BIND_MOVE_RIGHT, inputtype::keyboard, keycode::D);
|
||||
Events::bind(BIND_MOVE_LEFT, inputtype::keyboard, keycode::A);
|
||||
Events::bind(BIND_MOVE_JUMP, inputtype::keyboard, keycode::SPACE);
|
||||
Events::bind(BIND_MOVE_SPRINT, inputtype::keyboard, keycode::LEFT_CONTROL);
|
||||
Events::bind(BIND_MOVE_CROUCH, inputtype::keyboard, keycode::LEFT_SHIFT);
|
||||
Events::bind(BIND_MOVE_CHEAT, inputtype::keyboard, keycode::R);
|
||||
Events::bind(BIND_CAM_ZOOM, inputtype::keyboard, keycode::C);
|
||||
Events::bind(BIND_CAM_MODE, inputtype::keyboard, keycode::F4);
|
||||
Events::bind(BIND_PLAYER_NOCLIP, inputtype::keyboard, keycode::N);
|
||||
Events::bind(BIND_PLAYER_FLIGHT, inputtype::keyboard, keycode::F);
|
||||
Events::bind(BIND_PLAYER_ATTACK, inputtype::mouse, mousecode::BUTTON_1);
|
||||
Events::bind(BIND_PLAYER_BUILD, inputtype::mouse, mousecode::BUTTON_2);
|
||||
Events::bind(BIND_PLAYER_PICK, inputtype::mouse, mousecode::BUTTON_3);
|
||||
Events::bind(BIND_HUD_INVENTORY, inputtype::keyboard, keycode::TAB);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef CORE_DEFS_H_
|
||||
#define CORE_DEFS_H_
|
||||
#ifndef CORE_DEFS_HPP_
|
||||
#define CORE_DEFS_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -8,7 +8,8 @@ inline const std::string CORE_AIR = "core:air";
|
||||
|
||||
inline const std::string TEXTURE_NOTFOUND = "notfound";
|
||||
|
||||
/* bindings used in engine code */
|
||||
// built-in bindings
|
||||
inline const std::string BIND_DEVTOOLS_CONSOLE = "devtools.console";
|
||||
inline const std::string BIND_MOVE_FORWARD = "movement.forward";
|
||||
inline const std::string BIND_MOVE_BACK = "movement.back";
|
||||
inline const std::string BIND_MOVE_LEFT = "movement.left";
|
||||
@@ -29,8 +30,8 @@ inline const std::string BIND_HUD_INVENTORY = "hud.inventory";
|
||||
class ContentBuilder;
|
||||
|
||||
namespace corecontent {
|
||||
extern void setup_bindings();
|
||||
extern void setup(ContentBuilder* builder);
|
||||
void setup_bindings();
|
||||
void setup(ContentBuilder* builder);
|
||||
}
|
||||
|
||||
#endif // CORE_DEFS_H_
|
||||
#endif // CORE_DEFS_HPP_
|
||||
+152
-250
@@ -1,145 +1,103 @@
|
||||
#include "dynamic.h"
|
||||
#include "dynamic.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include "../coders/json.hpp"
|
||||
|
||||
using namespace dynamic;
|
||||
|
||||
List::~List() {
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::Value& value) {
|
||||
stream << json::stringify(value, false, " ");
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::Map_sptr& value) {
|
||||
stream << json::stringify(value, false, " ");
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::List_sptr& value) {
|
||||
stream << json::stringify(value, false, " ");
|
||||
return stream;
|
||||
}
|
||||
|
||||
std::string List::str(size_t index) const {
|
||||
const auto& val = values[index];
|
||||
switch (val->type) {
|
||||
case valtype::string: return *val->value.str;
|
||||
case valtype::boolean: return val->value.boolean ? "true" : "false";
|
||||
case valtype::number: return std::to_string(val->value.decimal);
|
||||
case valtype::integer: return std::to_string(val->value.integer);
|
||||
const auto& value = values[index];
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::string: return std::get<std::string>(value);
|
||||
case Type::boolean: return std::get<bool>(value) ? "true" : "false";
|
||||
case Type::number: return std::to_string(std::get<double>(value));
|
||||
case Type::integer: return std::to_string(std::get<int64_t>(value));
|
||||
default:
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
double List::num(size_t index) const {
|
||||
const auto& val = values[index];
|
||||
switch (val->type) {
|
||||
case valtype::number: return val->value.decimal;
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::string: return std::stoll(*val->value.str);
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
number_t List::num(size_t index) const {
|
||||
const auto& value = values[index];
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::number: return std::get<number_t>(value);
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::string: return std::stoll(std::get<std::string>(value));
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default:
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
int64_t List::integer(size_t index) const {
|
||||
const auto& val = values[index];
|
||||
switch (val->type) {
|
||||
case valtype::number: return val->value.decimal;
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::string: return std::stoll(*val->value.str);
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
integer_t List::integer(size_t index) const {
|
||||
const auto& value = values[index];
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::number: return std::get<number_t>(value);
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::string: return std::stoll(std::get<std::string>(value));
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default:
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
Map* List::map(size_t index) const {
|
||||
if (values[index]->type != valtype::map) {
|
||||
if (auto* val = std::get_if<Map_sptr>(&values[index])) {
|
||||
return val->get();
|
||||
} else {
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
return values[index]->value.map;
|
||||
}
|
||||
|
||||
List* List::list(size_t index) const {
|
||||
if (values[index]->type != valtype::list) {
|
||||
if (auto* val = std::get_if<List_sptr>(&values[index])) {
|
||||
return val->get();
|
||||
} else {
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
return values[index]->value.list;
|
||||
}
|
||||
|
||||
bool List::flag(size_t index) const {
|
||||
const auto& val = values[index];
|
||||
switch (val->type) {
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
const auto& value = values[index];
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default:
|
||||
throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
Value* List::getValueWriteable(size_t index) const {
|
||||
if (index > values.size()) {
|
||||
throw std::runtime_error("index error");
|
||||
}
|
||||
return values.at(index).get();
|
||||
Value* List::getValueWriteable(size_t index) {
|
||||
return &values.at(index);
|
||||
}
|
||||
|
||||
List& List::put(std::string value) {
|
||||
valvalue val;
|
||||
val.str = new std::string(value);
|
||||
values.push_back(std::make_unique<Value>(valtype::string, val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::put(uint value) {
|
||||
return put((int64_t)value);
|
||||
}
|
||||
|
||||
List& List::put(int value) {
|
||||
return put((int64_t)value);
|
||||
}
|
||||
|
||||
List& List::put(int64_t value) {
|
||||
valvalue val;
|
||||
val.integer = value;
|
||||
values.push_back(std::make_unique<Value>(valtype::integer, val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::put(uint64_t value) {
|
||||
return put((int64_t)value);
|
||||
}
|
||||
|
||||
List& List::put(double value) {
|
||||
valvalue val;
|
||||
val.decimal = value;
|
||||
values.push_back(std::make_unique<Value>(valtype::number, val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::put(float value) {
|
||||
return put((double)value);
|
||||
}
|
||||
|
||||
List& List::put(bool value) {
|
||||
valvalue val;
|
||||
val.boolean = value;
|
||||
values.push_back(std::make_unique<Value>(valtype::boolean, val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::put(Map* value) {
|
||||
valvalue val;
|
||||
val.map = value;
|
||||
values.push_back(std::make_unique<Value>(valtype::map, val));
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::put(List* value) {
|
||||
valvalue val;
|
||||
val.list = value;
|
||||
values.push_back(std::make_unique<Value>(valtype::list, val));
|
||||
List& List::put(const Value& value) {
|
||||
values.emplace_back(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
List& List::putList() {
|
||||
List* arr = new List();
|
||||
auto arr = create_list();
|
||||
put(arr);
|
||||
return *arr;
|
||||
}
|
||||
|
||||
Map& List::putMap() {
|
||||
Map* map = new Map();
|
||||
auto map = create_map();
|
||||
put(map);
|
||||
return *map;
|
||||
}
|
||||
@@ -148,232 +106,176 @@ void List::remove(size_t index) {
|
||||
values.erase(values.begin() + index);
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
void Map::str(const std::string& key, std::string& dst) const {
|
||||
dst = get(key, dst);
|
||||
}
|
||||
|
||||
void Map::str(std::string key, std::string& dst) const {
|
||||
dst = getStr(key, dst);
|
||||
}
|
||||
|
||||
std::string Map::getStr(std::string key) const {
|
||||
if (values.find(key) == values.end()) {
|
||||
throw std::runtime_error("missing key '"+key+"'");
|
||||
}
|
||||
return getStr(key, "");
|
||||
}
|
||||
|
||||
double Map::getNum(std::string key) const {
|
||||
if (values.find(key) == values.end()) {
|
||||
throw std::runtime_error("missing key '"+key+"'");
|
||||
}
|
||||
return getNum(key, 0);
|
||||
}
|
||||
|
||||
int64_t Map::getInt(std::string key) const {
|
||||
if (values.find(key) == values.end()) {
|
||||
throw std::runtime_error("missing key '"+key+"'");
|
||||
}
|
||||
return getInt(key, 0);
|
||||
}
|
||||
|
||||
bool Map::getBool(std::string key) const {
|
||||
if (values.find(key) == values.end()) {
|
||||
throw std::runtime_error("missing key '"+key+"'");
|
||||
}
|
||||
return getBool(key, false);
|
||||
}
|
||||
|
||||
std::string Map::getStr(std::string key, const std::string& def) const {
|
||||
std::string Map::get(const std::string& key, const std::string def) const {
|
||||
auto found = values.find(key);
|
||||
if (found == values.end())
|
||||
return def;
|
||||
auto& val = found->second;
|
||||
switch (val->type) {
|
||||
case valtype::string: return *val->value.str;
|
||||
case valtype::boolean: return val->value.boolean ? "true" : "false";
|
||||
case valtype::number: return std::to_string(val->value.decimal);
|
||||
case valtype::integer: return std::to_string(val->value.integer);
|
||||
default: throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
double Map::getNum(std::string key, double def) const {
|
||||
auto found = values.find(key);
|
||||
if (found == values.end())
|
||||
return def;
|
||||
auto& val = found->second;
|
||||
switch (val->type) {
|
||||
case valtype::number: return val->value.decimal;
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::string: return std::stoull(*val->value.str);
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
auto& value = found->second;
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::string: return std::get<std::string>(value);
|
||||
case Type::boolean: return std::get<bool>(value) ? "true" : "false";
|
||||
case Type::number: return std::to_string(std::get<number_t>(value));
|
||||
case Type::integer: return std::to_string(std::get<integer_t>(value));
|
||||
default: throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Map::getInt(std::string key, int64_t def) const {
|
||||
number_t Map::get(const std::string& key, double def) const {
|
||||
auto found = values.find(key);
|
||||
if (found == values.end())
|
||||
return def;
|
||||
auto& val = found->second;
|
||||
switch (val->type) {
|
||||
case valtype::number: return val->value.decimal;
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::string: return std::stoull(*val->value.str);
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
auto& value = found->second;
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::number: return std::get<number_t>(value);
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::string: return std::stoull(std::get<std::string>(value));
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default: throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
bool Map::getBool(std::string key, bool def) const {
|
||||
integer_t Map::get(const std::string& key, integer_t def) const {
|
||||
auto found = values.find(key);
|
||||
if (found == values.end())
|
||||
return def;
|
||||
auto& val = found->second;
|
||||
switch (val->type) {
|
||||
case valtype::integer: return val->value.integer;
|
||||
case valtype::boolean: return val->value.boolean;
|
||||
auto& value = found->second;
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::number: return std::get<number_t>(value);
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::string: return std::stoull(std::get<std::string>(value));
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default: throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
void Map::num(std::string key, double& dst) const {
|
||||
dst = getNum(key, dst);
|
||||
bool Map::get(const std::string& key, bool def) const {
|
||||
auto found = values.find(key);
|
||||
if (found == values.end())
|
||||
return def;
|
||||
auto& value = found->second;
|
||||
switch (static_cast<Type>(value.index())) {
|
||||
case Type::integer: return std::get<integer_t>(value);
|
||||
case Type::boolean: return std::get<bool>(value);
|
||||
default: throw std::runtime_error("type error");
|
||||
}
|
||||
}
|
||||
|
||||
void Map::num(std::string key, float& dst) const {
|
||||
dst = getNum(key, dst);
|
||||
void Map::num(const std::string& key, double& dst) const {
|
||||
dst = get(key, dst);
|
||||
}
|
||||
|
||||
void Map::num(std::string key, ubyte& dst) const {
|
||||
dst = getInt(key, dst);
|
||||
void Map::num(const std::string& key, float& dst) const {
|
||||
dst = get(key, static_cast<number_t>(dst));
|
||||
}
|
||||
|
||||
void Map::num(std::string key, int& dst) const {
|
||||
dst = getInt(key, dst);
|
||||
void Map::num(const std::string& key, ubyte& dst) const {
|
||||
dst = get(key, static_cast<integer_t>(dst));
|
||||
}
|
||||
|
||||
void Map::num(std::string key, int64_t& dst) const {
|
||||
dst = getInt(key, dst);
|
||||
void Map::num(const std::string& key, int& dst) const {
|
||||
dst = get(key, static_cast<integer_t>(dst));
|
||||
}
|
||||
|
||||
void Map::num(std::string key, uint64_t& dst) const {
|
||||
dst = getInt(key, dst);
|
||||
void Map::num(const std::string& key, int64_t& dst) const {
|
||||
dst = get(key, dst);
|
||||
}
|
||||
|
||||
void Map::num(std::string key, uint& dst) const {
|
||||
dst = getInt(key, dst);
|
||||
void Map::num(const std::string& key, uint64_t& dst) const {
|
||||
dst = get(key, static_cast<integer_t>(dst));
|
||||
}
|
||||
|
||||
Map* Map::map(std::string key) const {
|
||||
void Map::num(const std::string& key, uint& dst) const {
|
||||
dst = get(key, static_cast<integer_t>(dst));
|
||||
}
|
||||
|
||||
Map* Map::map(const std::string& key) const {
|
||||
auto found = values.find(key);
|
||||
if (found != values.end()) {
|
||||
auto& val = found->second;
|
||||
if (val->type != valtype::map)
|
||||
return nullptr;
|
||||
return val->value.map;
|
||||
if (auto* val = std::get_if<Map_sptr>(&found->second)) {
|
||||
return val->get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
List* Map::list(std::string key) const {
|
||||
List* Map::list(const std::string& key) const {
|
||||
auto found = values.find(key);
|
||||
if (found != values.end())
|
||||
return found->second->value.list;
|
||||
return std::get<List_sptr>(found->second).get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Map::flag(std::string key, bool& dst) const {
|
||||
dst = getBool(key, dst);
|
||||
void Map::flag(const std::string& key, bool& dst) const {
|
||||
dst = get(key, dst);
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, uint value) {
|
||||
return put(key, (int64_t)value);
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, int value) {
|
||||
return put(key, (int64_t)value);
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, int64_t value) {
|
||||
valvalue val;
|
||||
val.integer = value;
|
||||
values[key] = std::make_unique<Value>(valtype::integer, val);
|
||||
Map& Map::put(std::string key, const Value& value) {
|
||||
values.emplace(key, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, uint64_t value) {
|
||||
return put(key, (int64_t)value);
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, float value) {
|
||||
return put(key, (double)value);
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, double value) {
|
||||
valvalue val;
|
||||
val.decimal = value;
|
||||
values[key] = std::make_unique<Value>(valtype::number, val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, std::string value){
|
||||
valvalue val;
|
||||
val.str = new std::string(value);
|
||||
values[key] = std::make_unique<Value>(valtype::string, val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, const char* value) {
|
||||
return put(key, std::string(value));
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, Map* value){
|
||||
valvalue val;
|
||||
val.map = value;
|
||||
values[key] = std::make_unique<Value>(valtype::map, val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, List* value){
|
||||
valvalue val;
|
||||
val.list = value;
|
||||
values[key] = std::make_unique<Value>(valtype::list, val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Map& Map::put(std::string key, bool value){
|
||||
valvalue val;
|
||||
val.boolean = value;
|
||||
values[key] = std::make_unique<Value>(valtype::boolean, val);
|
||||
return *this;
|
||||
void Map::remove(const std::string& key) {
|
||||
values.erase(key);
|
||||
}
|
||||
|
||||
List& Map::putList(std::string key) {
|
||||
List* arr = new List();
|
||||
auto arr = create_list();
|
||||
put(key, arr);
|
||||
return *arr;
|
||||
}
|
||||
|
||||
Map& Map::putMap(std::string key) {
|
||||
Map* obj = new Map();
|
||||
auto obj = create_map();
|
||||
put(key, obj);
|
||||
return *obj;
|
||||
}
|
||||
|
||||
bool Map::has(std::string key) {
|
||||
bool Map::has(const std::string& key) const {
|
||||
return values.find(key) != values.end();
|
||||
}
|
||||
|
||||
Value::Value(valtype type, valvalue value) : type(type), value(value) {
|
||||
size_t Map::size() const {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
Value::~Value() {
|
||||
switch (type) {
|
||||
case valtype::map: delete value.map; break;
|
||||
case valtype::list: delete value.list; break;
|
||||
case valtype::string: delete value.str; break;
|
||||
default:
|
||||
break;
|
||||
static const std::string TYPE_NAMES[] {
|
||||
"none",
|
||||
"map",
|
||||
"list",
|
||||
"string",
|
||||
"number",
|
||||
"bool",
|
||||
"integer",
|
||||
};
|
||||
|
||||
const std::string& dynamic::type_name(const Value& value) {
|
||||
return TYPE_NAMES[value.index()];
|
||||
}
|
||||
|
||||
List_sptr dynamic::create_list(std::initializer_list<Value> values) {
|
||||
return std::make_shared<List>(values);
|
||||
}
|
||||
|
||||
Map_sptr dynamic::create_map(std::initializer_list<std::pair<const std::string, Value>> entries) {
|
||||
return std::make_shared<Map>(entries);
|
||||
}
|
||||
|
||||
number_t dynamic::get_number(const Value& value) {
|
||||
if (auto num = std::get_if<number_t>(&value)) {
|
||||
return *num;
|
||||
} else if (auto num = std::get_if<integer_t>(&value)) {
|
||||
return *num;
|
||||
}
|
||||
throw std::runtime_error("cannot cast "+type_name(value)+" to number");
|
||||
}
|
||||
|
||||
integer_t dynamic::get_integer(const Value& value) {
|
||||
if (auto num = std::get_if<integer_t>(&value)) {
|
||||
return *num;
|
||||
}
|
||||
throw std::runtime_error("cannot cast "+type_name(value)+" to integer");
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
#ifndef DATA_DYNAMIC_H_
|
||||
#define DATA_DYNAMIC_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "../typedefs.h"
|
||||
|
||||
namespace dynamic {
|
||||
class Map;
|
||||
class List;
|
||||
class Value;
|
||||
|
||||
enum class valtype {
|
||||
map, list, number, integer, string, boolean
|
||||
};
|
||||
|
||||
union valvalue {
|
||||
Map* map;
|
||||
List* list;
|
||||
std::string* str;
|
||||
double decimal;
|
||||
int64_t integer;
|
||||
uint64_t boolean;
|
||||
};
|
||||
|
||||
class Value {
|
||||
public:
|
||||
valtype type;
|
||||
valvalue value;
|
||||
Value(valtype type, valvalue value);
|
||||
~Value();
|
||||
};
|
||||
|
||||
class List {
|
||||
public:
|
||||
std::vector<std::unique_ptr<Value>> values;
|
||||
~List();
|
||||
|
||||
std::string str(size_t index) const;
|
||||
double num(size_t index) const;
|
||||
int64_t integer(size_t index) const;
|
||||
Map* map(size_t index) const;
|
||||
List* list(size_t index) const;
|
||||
bool flag(size_t index) const;
|
||||
|
||||
inline size_t size() const {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
inline Value* get(size_t i) const {
|
||||
return values.at(i).get();
|
||||
}
|
||||
|
||||
List& put(uint value);
|
||||
List& put(int value);
|
||||
List& put(uint64_t value);
|
||||
List& put(int64_t value);
|
||||
List& put(float value);
|
||||
List& put(double value);
|
||||
List& put(std::string value);
|
||||
List& put(Map* value);
|
||||
List& put(List* value);
|
||||
List& put(bool value);
|
||||
|
||||
Value* getValueWriteable(size_t index) const;
|
||||
|
||||
List& putList();
|
||||
Map& putMap();
|
||||
|
||||
void remove(size_t index);
|
||||
};
|
||||
|
||||
class Map {
|
||||
public:
|
||||
std::unordered_map<std::string, std::unique_ptr<Value>> values;
|
||||
~Map();
|
||||
|
||||
std::string getStr(std::string key) const;
|
||||
double getNum(std::string key) const;
|
||||
int64_t getInt(std::string key) const;
|
||||
bool getBool(std::string key) const;
|
||||
|
||||
std::string getStr(std::string key, const std::string& def) const;
|
||||
double getNum(std::string key, double def) const;
|
||||
int64_t getInt(std::string key, int64_t def) const;
|
||||
bool getBool(std::string key, bool def) const;
|
||||
|
||||
void str(std::string key, std::string& dst) const;
|
||||
void num(std::string key, int& dst) const;
|
||||
void num(std::string key, float& dst) const;
|
||||
void num(std::string key, uint& dst) const;
|
||||
void num(std::string key, int64_t& dst) const;
|
||||
void num(std::string key, uint64_t& dst) const;
|
||||
void num(std::string key, ubyte& dst) const;
|
||||
void num(std::string key, double& dst) const;
|
||||
Map* map(std::string key) const;
|
||||
List* list(std::string key) const;
|
||||
void flag(std::string key, bool& dst) const;
|
||||
|
||||
Map& put(std::string key, uint value);
|
||||
Map& put(std::string key, int value);
|
||||
Map& put(std::string key, int64_t value);
|
||||
Map& put(std::string key, uint64_t value);
|
||||
Map& put(std::string key, float value);
|
||||
Map& put(std::string key, double value);
|
||||
Map& put(std::string key, const char* value);
|
||||
Map& put(std::string key, std::string value);
|
||||
Map& put(std::string key, Map* value);
|
||||
Map& put(std::string key, List* value);
|
||||
Map& put(std::string key, bool value);
|
||||
|
||||
List& putList(std::string key);
|
||||
Map& putMap(std::string key);
|
||||
|
||||
bool has(std::string key);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DATA_DYNAMIC_H_
|
||||
@@ -0,0 +1,170 @@
|
||||
#ifndef DATA_DYNAMIC_HPP_
|
||||
#define DATA_DYNAMIC_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace dynamic {
|
||||
class Map;
|
||||
class List;
|
||||
|
||||
enum class Type {
|
||||
none=0, map, list, string, number, boolean, integer
|
||||
};
|
||||
|
||||
using Map_sptr = std::shared_ptr<Map>;
|
||||
using List_sptr = std::shared_ptr<List>;
|
||||
|
||||
struct none {};
|
||||
|
||||
inline constexpr none NONE = {};
|
||||
|
||||
using Value = std::variant<
|
||||
none,
|
||||
Map_sptr,
|
||||
List_sptr,
|
||||
std::string,
|
||||
number_t,
|
||||
bool,
|
||||
integer_t
|
||||
>;
|
||||
|
||||
const std::string& type_name(const Value& value);
|
||||
List_sptr create_list(std::initializer_list<Value> values={});
|
||||
Map_sptr create_map(std::initializer_list<std::pair<const std::string, Value>> entries={});
|
||||
number_t get_number(const Value& value);
|
||||
integer_t get_integer(const Value& value);
|
||||
|
||||
inline bool is_numeric(const Value& value) {
|
||||
return std::holds_alternative<number_t>(value) ||
|
||||
std::holds_alternative<integer_t>(value);
|
||||
}
|
||||
|
||||
class List {
|
||||
public:
|
||||
std::vector<Value> values;
|
||||
|
||||
List() {}
|
||||
List(std::vector<Value> values) : values(std::move(values)) {}
|
||||
|
||||
std::string str(size_t index) const;
|
||||
number_t num(size_t index) const;
|
||||
integer_t integer(size_t index) const;
|
||||
Map* map(size_t index) const;
|
||||
List* list(size_t index) const;
|
||||
bool flag(size_t index) const;
|
||||
|
||||
inline size_t size() const {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
inline Value& get(size_t i) {
|
||||
return values.at(i);
|
||||
}
|
||||
|
||||
List& put(std::unique_ptr<Map> value) {
|
||||
return put(Map_sptr(value.release()));
|
||||
}
|
||||
List& put(std::unique_ptr<List> value) {
|
||||
return put(List_sptr(value.release()));
|
||||
}
|
||||
List& put(const Value& value);
|
||||
|
||||
Value* getValueWriteable(size_t index);
|
||||
|
||||
List& putList();
|
||||
Map& putMap();
|
||||
|
||||
void remove(size_t index);
|
||||
};
|
||||
|
||||
class Map {
|
||||
public:
|
||||
std::unordered_map<std::string, Value> values;
|
||||
|
||||
Map() {}
|
||||
Map(std::unordered_map<std::string, Value> values)
|
||||
: values(std::move(values)) {};
|
||||
|
||||
template<typename T>
|
||||
T get(const std::string& key) const {
|
||||
if (!has(key)) {
|
||||
throw std::runtime_error("missing key '"+key+"'");
|
||||
}
|
||||
return get(key, T());
|
||||
}
|
||||
|
||||
std::string get(const std::string& key, const std::string def) const;
|
||||
number_t get(const std::string& key, double def) const;
|
||||
integer_t get(const std::string& key, integer_t def) const;
|
||||
bool get(const std::string& key, bool def) const;
|
||||
|
||||
int get(const std::string& key, int def) const {
|
||||
return get(key, static_cast<integer_t>(def));
|
||||
}
|
||||
uint get(const std::string& key, uint def) const {
|
||||
return get(key, static_cast<integer_t>(def));
|
||||
}
|
||||
uint64_t get(const std::string& key, uint64_t def) const {
|
||||
return get(key, static_cast<integer_t>(def));
|
||||
}
|
||||
|
||||
void str(const std::string& key, std::string& dst) const;
|
||||
void num(const std::string& key, int& dst) const;
|
||||
void num(const std::string& key, float& dst) const;
|
||||
void num(const std::string& key, uint& dst) const;
|
||||
void num(const std::string& key, int64_t& dst) const;
|
||||
void num(const std::string& key, uint64_t& dst) const;
|
||||
void num(const std::string& key, ubyte& dst) const;
|
||||
void num(const std::string& key, double& dst) const;
|
||||
Map* map(const std::string& key) const;
|
||||
List* list(const std::string& key) const;
|
||||
void flag(const std::string& key, bool& dst) const;
|
||||
|
||||
Map& put(std::string key, std::unique_ptr<Map> value) {
|
||||
return put(key, Map_sptr(value.release()));
|
||||
}
|
||||
Map& put(std::string key, std::unique_ptr<List> value) {
|
||||
return put(key, List_sptr(value.release()));
|
||||
}
|
||||
Map& put(std::string key, int value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, unsigned int value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, int64_t value) {
|
||||
return put(key, Value(static_cast<integer_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, float value) {
|
||||
return put(key, Value(static_cast<number_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, double value) {
|
||||
return put(key, Value(static_cast<number_t>(value)));
|
||||
}
|
||||
Map& put(std::string key, bool value) {
|
||||
return put(key, Value(static_cast<bool>(value)));
|
||||
}
|
||||
Map& put(std::string key, const Value& value);
|
||||
|
||||
void remove(const std::string& key);
|
||||
|
||||
List& putList(std::string key);
|
||||
Map& putMap(std::string key);
|
||||
|
||||
bool has(const std::string& key) const;
|
||||
size_t size() const;
|
||||
};
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::Value& value);
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::Map_sptr& value);
|
||||
std::ostream& operator<<(std::ostream& stream, const dynamic::List_sptr& value);
|
||||
|
||||
#endif // DATA_DYNAMIC_HPP_
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "setting.hpp"
|
||||
|
||||
#include "../util/stringutil.hpp"
|
||||
|
||||
std::string NumberSetting::toString() const {
|
||||
switch (getFormat()) {
|
||||
case setting_format::simple:
|
||||
return util::to_string(value);
|
||||
case setting_format::percent:
|
||||
return std::to_string(static_cast<integer_t>(round(value * 100))) + "%";
|
||||
default:
|
||||
return "invalid format";
|
||||
}
|
||||
}
|
||||
|
||||
std::string IntegerSetting::toString() const {
|
||||
switch (getFormat()) {
|
||||
case setting_format::simple:
|
||||
return util::to_string(value);
|
||||
case setting_format::percent:
|
||||
return std::to_string(value) + "%";
|
||||
default:
|
||||
return "invalid format";
|
||||
}
|
||||
}
|
||||
|
||||
std::string FlagSetting::toString() const {
|
||||
switch (getFormat()) {
|
||||
case setting_format::simple:
|
||||
return value ? "true" : "false";
|
||||
default:
|
||||
return "invalid format";
|
||||
}
|
||||
}
|
||||
|
||||
std::string StringSetting::toString() const {
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
#ifndef DATA_SETTING_HPP_
|
||||
#define DATA_SETTING_HPP_
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
#include "../delegates.hpp"
|
||||
|
||||
enum class setting_format {
|
||||
simple, percent
|
||||
};
|
||||
|
||||
class Setting {
|
||||
protected:
|
||||
setting_format format;
|
||||
public:
|
||||
Setting(setting_format format) : format(format) {
|
||||
}
|
||||
|
||||
virtual ~Setting() {}
|
||||
|
||||
virtual void resetToDefault() = 0;
|
||||
|
||||
virtual setting_format getFormat() const {
|
||||
return format;
|
||||
}
|
||||
|
||||
virtual std::string toString() const = 0;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class ObservableSetting : public Setting {
|
||||
int nextid = 1;
|
||||
std::unordered_map<int, consumer<T>> observers;
|
||||
protected:
|
||||
T initial;
|
||||
T value;
|
||||
public:
|
||||
ObservableSetting(T value, setting_format format)
|
||||
: Setting(format), initial(value), value(value) {}
|
||||
|
||||
observer_handler observe(consumer<T> callback, bool callOnStart=false) {
|
||||
const int id = nextid++;
|
||||
observers.emplace(id, callback);
|
||||
if (callOnStart) {
|
||||
callback(value);
|
||||
}
|
||||
return std::shared_ptr<int>(new int(id), [this](int* id) {
|
||||
observers.erase(*id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
const T& get() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return value;
|
||||
}
|
||||
|
||||
void notify(T value) {
|
||||
for (auto& entry : observers) {
|
||||
entry.second(value);
|
||||
}
|
||||
}
|
||||
|
||||
void set(T value) {
|
||||
if (value == this->value) {
|
||||
return;
|
||||
}
|
||||
this->value = value;
|
||||
notify(value);
|
||||
}
|
||||
|
||||
virtual void resetToDefault() override {
|
||||
set(initial);
|
||||
}
|
||||
};
|
||||
|
||||
class NumberSetting : public ObservableSetting<number_t> {
|
||||
protected:
|
||||
number_t min;
|
||||
number_t max;
|
||||
public:
|
||||
NumberSetting(
|
||||
number_t value,
|
||||
number_t min=std::numeric_limits<number_t>::min(),
|
||||
number_t max=std::numeric_limits<number_t>::max(),
|
||||
setting_format format=setting_format::simple
|
||||
) : ObservableSetting(value, format),
|
||||
min(min),
|
||||
max(max)
|
||||
{}
|
||||
|
||||
number_t& operator*() {
|
||||
return value;
|
||||
}
|
||||
|
||||
number_t get() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
number_t getMin() const {
|
||||
return min;
|
||||
}
|
||||
|
||||
number_t getMax() const {
|
||||
return max;
|
||||
}
|
||||
|
||||
number_t getT() const {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
|
||||
static inline NumberSetting createPercent(number_t def) {
|
||||
return NumberSetting(def, 0.0, 1.0, setting_format::percent);
|
||||
}
|
||||
};
|
||||
|
||||
class IntegerSetting : public ObservableSetting<integer_t> {
|
||||
protected:
|
||||
integer_t min;
|
||||
integer_t max;
|
||||
public:
|
||||
IntegerSetting(
|
||||
integer_t value,
|
||||
integer_t min=std::numeric_limits<integer_t>::min(),
|
||||
integer_t max=std::numeric_limits<integer_t>::max(),
|
||||
setting_format format=setting_format::simple
|
||||
) : ObservableSetting(value, format),
|
||||
min(min),
|
||||
max(max)
|
||||
{}
|
||||
|
||||
integer_t getMin() const {
|
||||
return min;
|
||||
}
|
||||
|
||||
integer_t getMax() const {
|
||||
return max;
|
||||
}
|
||||
|
||||
integer_t getT() const {
|
||||
return (value - min) / (max - min);
|
||||
}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
};
|
||||
|
||||
class FlagSetting : public ObservableSetting<bool> {
|
||||
public:
|
||||
FlagSetting(
|
||||
bool value,
|
||||
setting_format format=setting_format::simple
|
||||
) : ObservableSetting(value, format) {}
|
||||
|
||||
void toggle() {
|
||||
set(!get());
|
||||
}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
};
|
||||
|
||||
class StringSetting : public ObservableSetting<std::string> {
|
||||
public:
|
||||
StringSetting(
|
||||
std::string value,
|
||||
setting_format format=setting_format::simple
|
||||
) : ObservableSetting(value, format) {}
|
||||
|
||||
virtual std::string toString() const override;
|
||||
};
|
||||
|
||||
#endif // DATA_SETTING_HPP_
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "Logger.hpp"
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
using namespace debug;
|
||||
|
||||
std::ofstream Logger::file;
|
||||
std::mutex Logger::mutex;
|
||||
std::string Logger::utcOffset = "";
|
||||
unsigned Logger::moduleLen = 20;
|
||||
|
||||
LogMessage::~LogMessage() {
|
||||
logger->log(level, ss.str());
|
||||
}
|
||||
|
||||
Logger::Logger(std::string name) : name(name) {
|
||||
}
|
||||
|
||||
void Logger::log(LogLevel level, const std::string& name, std::string message) {
|
||||
using namespace std::chrono;
|
||||
|
||||
std::stringstream ss;
|
||||
switch (level) {
|
||||
case LogLevel::debug:
|
||||
# ifdef NDEBUG
|
||||
return;
|
||||
# endif
|
||||
ss << "[D]";
|
||||
break;
|
||||
case LogLevel::info:
|
||||
ss << "[I]";
|
||||
break;
|
||||
case LogLevel::warning:
|
||||
ss << "[W]";
|
||||
break;
|
||||
case LogLevel::error:
|
||||
ss << "[E]";
|
||||
break;
|
||||
}
|
||||
time_t tm = std::time(nullptr);
|
||||
auto ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch()) % 1000;
|
||||
ss << " " << std::put_time(std::localtime(&tm), "%Y/%m/%d %T");
|
||||
ss << '.' << std::setfill('0') << std::setw(3) << ms.count();
|
||||
ss << utcOffset << " (" << std::setfill(' ') << std::setw(moduleLen) << name << ") ";
|
||||
ss << message;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
auto string = ss.str();
|
||||
if (file.good()) {
|
||||
file << string << '\n';
|
||||
}
|
||||
std::cout << string << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::init(const std::string& filename) {
|
||||
file.open(filename);
|
||||
|
||||
time_t tm = std::time(nullptr);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&tm), "%z");
|
||||
utcOffset = ss.str();
|
||||
}
|
||||
|
||||
void Logger::flush() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
file.flush();
|
||||
}
|
||||
|
||||
void Logger::log(LogLevel level, std::string message) {
|
||||
log(level, name, message);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef DEBUG_LOGGER_HPP_
|
||||
#define DEBUG_LOGGER_HPP_
|
||||
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
||||
namespace debug {
|
||||
enum class LogLevel {
|
||||
debug, info, warning, error
|
||||
};
|
||||
|
||||
class Logger;
|
||||
|
||||
class LogMessage {
|
||||
Logger* logger;
|
||||
LogLevel level;
|
||||
std::stringstream ss;
|
||||
public:
|
||||
LogMessage(Logger* logger, LogLevel level) : logger(logger), level(level) {}
|
||||
~LogMessage();
|
||||
|
||||
template<class T>
|
||||
LogMessage& operator<<(const T& x) {
|
||||
ss << x;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class Logger {
|
||||
static std::mutex mutex;
|
||||
static std::string utcOffset;
|
||||
static std::ofstream file;
|
||||
static unsigned moduleLen;
|
||||
|
||||
std::string name;
|
||||
|
||||
static void log(LogLevel level, const std::string& name, std::string message);
|
||||
public:
|
||||
static void init(const std::string& filename);
|
||||
static void flush();
|
||||
|
||||
Logger(std::string name);
|
||||
|
||||
void log(LogLevel level, std::string message);
|
||||
|
||||
LogMessage debug() {
|
||||
return LogMessage(this, LogLevel::debug);
|
||||
}
|
||||
|
||||
LogMessage info() {
|
||||
return LogMessage(this, LogLevel::info);
|
||||
}
|
||||
|
||||
LogMessage error() {
|
||||
return LogMessage(this, LogLevel::error);
|
||||
}
|
||||
|
||||
LogMessage warning() {
|
||||
return LogMessage(this, LogLevel::warning);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DEBUG_LOGGER_HPP_
|
||||
@@ -1,11 +1,13 @@
|
||||
#ifndef DELEGATES_H_
|
||||
#define DELEGATES_H_
|
||||
#ifndef DELEGATES_HPP_
|
||||
#define DELEGATES_HPP_
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
using runnable = std::function<void()>;
|
||||
template<class T> using supplier = std::function<T()>;
|
||||
template<class T> using consumer = std::function<void(T)>;
|
||||
|
||||
// data sources
|
||||
using wstringsupplier = std::function<std::wstring()>;
|
||||
@@ -20,4 +22,4 @@ using boolconsumer = std::function<void(bool)>;
|
||||
using int_array_consumer = std::function<void(const int[], size_t)>;
|
||||
using wstringchecker = std::function<bool(const std::wstring&)>;
|
||||
|
||||
#endif // DELEGATES_H_
|
||||
#endif // DELEGATES_HPP_
|
||||
+238
-168
@@ -1,47 +1,47 @@
|
||||
#include "engine.h"
|
||||
#include "engine.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#define GLEW_STATIC
|
||||
|
||||
#include "audio/audio.h"
|
||||
#include "assets/Assets.h"
|
||||
#include "assets/AssetsLoader.h"
|
||||
#include "world/WorldGenerators.h"
|
||||
#include "voxels/DefaultWorldGenerator.h"
|
||||
#include "voxels/FlatWorldGenerator.h"
|
||||
#include "window/Window.h"
|
||||
#include "window/Events.h"
|
||||
#include "window/Camera.h"
|
||||
#include "window/input.h"
|
||||
#include "graphics/Batch2D.h"
|
||||
#include "graphics/GfxContext.h"
|
||||
#include "graphics/Shader.h"
|
||||
#include "graphics/ImageData.h"
|
||||
#include "frontend/gui/GUI.h"
|
||||
#include "frontend/screens.h"
|
||||
#include "frontend/menu/menu.h"
|
||||
#include "util/platform.h"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "assets/AssetsLoader.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "coders/GLSLExtension.hpp"
|
||||
#include "coders/imageio.hpp"
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
#include "content/ContentLoader.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "files/files.hpp"
|
||||
#include "files/settings_io.hpp"
|
||||
#include "frontend/locale.hpp"
|
||||
#include "frontend/menu.hpp"
|
||||
#include "frontend/screens/Screen.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "logic/EngineController.hpp"
|
||||
#include "logic/CommandsInterpreter.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "voxels/DefaultWorldGenerator.hpp"
|
||||
#include "voxels/FlatWorldGenerator.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "window/Events.hpp"
|
||||
#include "window/input.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/WorldGenerators.hpp"
|
||||
|
||||
#include "coders/json.h"
|
||||
#include "coders/png.h"
|
||||
#include "coders/GLSLExtension.h"
|
||||
#include "files/files.h"
|
||||
#include "files/engine_paths.h"
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
|
||||
#include "content/Content.h"
|
||||
#include "content/ContentPack.h"
|
||||
#include "content/ContentLoader.h"
|
||||
#include "frontend/locale/langs.h"
|
||||
#include "logic/scripting/scripting.h"
|
||||
|
||||
#include "core_defs.h"
|
||||
static debug::Logger logger("engine");
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -50,48 +50,68 @@ void addWorldGenerators() {
|
||||
WorldGenerators::addGenerator<FlatWorldGenerator>("core:flat");
|
||||
}
|
||||
|
||||
Engine::Engine(EngineSettings& settings, EnginePaths* paths)
|
||||
: settings(settings), paths(paths)
|
||||
{
|
||||
if (Window::initialize(settings.display)){
|
||||
inline void create_channel(Engine* engine, std::string name, NumberSetting& setting) {
|
||||
if (name != "master") {
|
||||
audio::create_channel(name);
|
||||
}
|
||||
engine->keepAlive(setting.observe([=](auto value) {
|
||||
audio::get_channel(name)->setVolume(value*value);
|
||||
}));
|
||||
}
|
||||
|
||||
Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths)
|
||||
: settings(settings), settingsHandler(settingsHandler), paths(paths),
|
||||
interpreter(std::make_unique<cmd::CommandsInterpreter>())
|
||||
{
|
||||
corecontent::setup_bindings();
|
||||
loadSettings();
|
||||
|
||||
controller = std::make_unique<EngineController>(this);
|
||||
if (Window::initialize(&this->settings.display)){
|
||||
throw initialize_error("could not initialize window");
|
||||
}
|
||||
audio::initialize(settings.audio.enabled);
|
||||
audio::create_channel("regular");
|
||||
audio::create_channel("music");
|
||||
audio::create_channel("ambient");
|
||||
audio::create_channel("ui");
|
||||
audio::initialize(settings.audio.enabled.get());
|
||||
create_channel(this, "master", settings.audio.volumeMaster);
|
||||
create_channel(this, "regular", settings.audio.volumeRegular);
|
||||
create_channel(this, "music", settings.audio.volumeMusic);
|
||||
create_channel(this, "ambient", settings.audio.volumeAmbient);
|
||||
create_channel(this, "ui", settings.audio.volumeUI);
|
||||
|
||||
auto resdir = paths->getResources();
|
||||
scripting::initialize(this);
|
||||
|
||||
std::cout << "-- loading assets" << std::endl;
|
||||
std::vector<fs::path> roots {resdir};
|
||||
|
||||
resPaths = std::make_unique<ResPaths>(resdir, roots);
|
||||
assets = std::make_unique<Assets>();
|
||||
|
||||
AssetsLoader loader(assets.get(), resPaths.get());
|
||||
AssetsLoader::addDefaults(loader, nullptr);
|
||||
|
||||
Shader::preprocessor->setPaths(resPaths.get());
|
||||
while (loader.hasNext()) {
|
||||
if (!loader.loadNext()) {
|
||||
assets.reset();
|
||||
scripting::close();
|
||||
Window::terminate();
|
||||
throw initialize_error("could not to load assets");
|
||||
}
|
||||
}
|
||||
gui = std::make_unique<gui::GUI>();
|
||||
if (settings.ui.language == "auto") {
|
||||
settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources());
|
||||
if (settings.ui.language.get() == "auto") {
|
||||
settings.ui.language.set(langs::locale_by_envlocale(
|
||||
platform::detect_locale(),
|
||||
paths->getResources()
|
||||
));
|
||||
}
|
||||
if (ENGINE_VERSION_INDEV) {
|
||||
if (ENGINE_DEBUG_BUILD) {
|
||||
menus::create_version_label(this);
|
||||
}
|
||||
setLanguage(settings.ui.language);
|
||||
keepAlive(settings.ui.language.observe([=](auto lang) {
|
||||
setLanguage(lang);
|
||||
}, true));
|
||||
addWorldGenerators();
|
||||
|
||||
scripting::initialize(this);
|
||||
}
|
||||
|
||||
void Engine::loadSettings() {
|
||||
fs::path settings_file = paths->getSettingsFile();
|
||||
if (fs::is_regular_file(settings_file)) {
|
||||
logger.info() << "loading settings";
|
||||
std::string text = files::read_string(settings_file);
|
||||
toml::parse(settingsHandler, settings_file.string(), text);
|
||||
}
|
||||
fs::path controls_file = paths->getControlsFile();
|
||||
if (fs::is_regular_file(controls_file)) {
|
||||
logger.info() << "loading controls";
|
||||
std::string text = files::read_string(controls_file);
|
||||
Events::loadBindings(controls_file.u8string(), text);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::onAssetsLoaded() {
|
||||
gui->onAssetsLoad(assets.get());
|
||||
}
|
||||
|
||||
void Engine::updateTimers() {
|
||||
@@ -103,86 +123,132 @@ void Engine::updateTimers() {
|
||||
|
||||
void Engine::updateHotkeys() {
|
||||
if (Events::jpressed(keycode::F2)) {
|
||||
std::unique_ptr<ImageData> image(Window::takeScreenshot());
|
||||
image->flipY();
|
||||
fs::path filename = paths->getScreenshotFile("png");
|
||||
png::write_image(filename.string(), image.get());
|
||||
std::cout << "saved screenshot as " << filename << std::endl;
|
||||
saveScreenshot();
|
||||
}
|
||||
if (Events::jpressed(keycode::F11)) {
|
||||
Window::toggleFullscreen();
|
||||
settings.display.fullscreen.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr float sqr(float x) {
|
||||
return x*x;
|
||||
}
|
||||
|
||||
static void updateAudio(double delta, const AudioSettings& settings) {
|
||||
audio::get_channel("master")->setVolume(sqr(settings.volumeMaster));
|
||||
audio::get_channel("regular")->setVolume(sqr(settings.volumeRegular));
|
||||
audio::get_channel("ui")->setVolume(sqr(settings.volumeUI));
|
||||
audio::get_channel("ambient")->setVolume(sqr(settings.volumeAmbient));
|
||||
audio::get_channel("music")->setVolume(sqr(settings.volumeMusic));
|
||||
audio::update(delta);
|
||||
void Engine::saveScreenshot() {
|
||||
auto image = Window::takeScreenshot();
|
||||
image->flipY();
|
||||
fs::path filename = paths->getScreenshotFile("png");
|
||||
imageio::write(filename.string(), image.get());
|
||||
logger.info() << "saved screenshot as " << filename.u8string();
|
||||
}
|
||||
|
||||
void Engine::mainloop() {
|
||||
logger.info() << "starting menu screen";
|
||||
setScreen(std::make_shared<MenuScreen>(this));
|
||||
|
||||
Batch2D batch(1024);
|
||||
lastTime = Window::time();
|
||||
|
||||
std::cout << "-- initialized" << std::endl;
|
||||
|
||||
logger.info() << "engine started";
|
||||
while (!Window::isShouldClose()){
|
||||
assert(screen != nullptr);
|
||||
updateTimers();
|
||||
updateHotkeys();
|
||||
updateAudio(delta, settings.audio);
|
||||
audio::update(delta);
|
||||
|
||||
gui->act(delta);
|
||||
gui->act(delta, Viewport(Window::width, Window::height));
|
||||
screen->update(delta);
|
||||
|
||||
if (!Window::isIconified()) {
|
||||
screen->draw(delta);
|
||||
|
||||
Viewport viewport(Window::width, Window::height);
|
||||
GfxContext ctx(nullptr, viewport, &batch);
|
||||
gui->draw(&ctx, assets.get());
|
||||
|
||||
Window::swapInterval(settings.display.swapInterval);
|
||||
} else {
|
||||
Window::swapInterval(1);
|
||||
renderFrame(batch);
|
||||
}
|
||||
Window::swapInterval(Window::isIconified() ? 1 : settings.display.vsync.get());
|
||||
|
||||
processPostRunnables();
|
||||
|
||||
Window::swapBuffers();
|
||||
Events::pollEvents();
|
||||
}
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
std::cout << "-- shutting down" << std::endl;
|
||||
if (screen) {
|
||||
screen->onEngineShutdown();
|
||||
}
|
||||
screen.reset();
|
||||
content.reset();
|
||||
assets.reset();
|
||||
audio::close();
|
||||
scripting::close();
|
||||
Window::terminate();
|
||||
std::cout << "-- engine finished" << std::endl;
|
||||
void Engine::renderFrame(Batch2D& batch) {
|
||||
screen->draw(delta);
|
||||
|
||||
Viewport viewport(Window::width, Window::height);
|
||||
DrawContext ctx(nullptr, viewport, &batch);
|
||||
gui->draw(&ctx, assets.get());
|
||||
}
|
||||
|
||||
inline const std::string checkPacks(
|
||||
const std::unordered_set<std::string>& packs,
|
||||
const std::vector<std::string>& dependencies
|
||||
) {
|
||||
for (const std::string& str : dependencies) {
|
||||
if (packs.find(str) == packs.end()) {
|
||||
return str;
|
||||
void Engine::processPostRunnables() {
|
||||
std::lock_guard<std::recursive_mutex> lock(postRunnablesMutex);
|
||||
while (!postRunnables.empty()) {
|
||||
postRunnables.front()();
|
||||
postRunnables.pop();
|
||||
}
|
||||
scripting::process_post_runnables();
|
||||
}
|
||||
|
||||
void Engine::saveSettings() {
|
||||
logger.info() << "saving settings";
|
||||
files::write_string(paths->getSettingsFile(), toml::stringify(settingsHandler));
|
||||
logger.info() << "saving bindings";
|
||||
files::write_string(paths->getControlsFile(), Events::writeBindings());
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
saveSettings();
|
||||
logger.info() << "shutting down";
|
||||
if (screen) {
|
||||
screen->onEngineShutdown();
|
||||
screen.reset();
|
||||
}
|
||||
content.reset();
|
||||
assets.reset();
|
||||
interpreter.reset();
|
||||
gui.reset();
|
||||
logger.info() << "gui finished";
|
||||
audio::close();
|
||||
scripting::close();
|
||||
logger.info() << "scripting finished";
|
||||
Window::terminate();
|
||||
logger.info() << "engine finished";
|
||||
}
|
||||
|
||||
EngineController* Engine::getController() {
|
||||
return controller.get();
|
||||
}
|
||||
|
||||
cmd::CommandsInterpreter* Engine::getCommandsInterpreter() {
|
||||
return interpreter.get();
|
||||
}
|
||||
|
||||
PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
|
||||
PacksManager manager;
|
||||
manager.setSources({
|
||||
worldFolder/fs::path("content"),
|
||||
paths->getUserfiles()/fs::path("content"),
|
||||
paths->getResources()/fs::path("content")
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
void Engine::loadAssets() {
|
||||
logger.info() << "loading assets";
|
||||
Shader::preprocessor->setPaths(resPaths.get());
|
||||
|
||||
auto new_assets = std::make_unique<Assets>();
|
||||
AssetsLoader loader(new_assets.get(), resPaths.get());
|
||||
AssetsLoader::addDefaults(loader, content.get());
|
||||
|
||||
bool threading = false;
|
||||
if (threading) {
|
||||
auto task = loader.startTask([=](){});
|
||||
task->waitForEnd();
|
||||
} else {
|
||||
while (loader.hasNext()) {
|
||||
if (!loader.loadNext()) {
|
||||
new_assets.reset();
|
||||
throw std::runtime_error("could not to load assets");
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
assets.reset(new_assets.release());
|
||||
}
|
||||
|
||||
void Engine::loadContent() {
|
||||
@@ -191,65 +257,57 @@ void Engine::loadContent() {
|
||||
corecontent::setup(&contentBuilder);
|
||||
paths->setContentPacks(&contentPacks);
|
||||
|
||||
std::vector<fs::path> resRoots;
|
||||
std::vector<ContentPack> srcPacks = contentPacks;
|
||||
contentPacks.clear();
|
||||
|
||||
std::string missingDependency;
|
||||
std::unordered_set<std::string> loadedPacks, existingPacks;
|
||||
for (const auto& item : srcPacks) {
|
||||
existingPacks.insert(item.id);
|
||||
std::vector<std::string> names;
|
||||
for (auto& pack : contentPacks) {
|
||||
names.push_back(pack.id);
|
||||
}
|
||||
PacksManager manager = createPacksManager(paths->getWorldFolder());
|
||||
manager.scan();
|
||||
names = manager.assembly(names);
|
||||
contentPacks = manager.getAll(names);
|
||||
|
||||
while (existingPacks.size() > loadedPacks.size()) {
|
||||
for (auto& pack : srcPacks) {
|
||||
if(loadedPacks.find(pack.id) != loadedPacks.end()) {
|
||||
continue;
|
||||
}
|
||||
missingDependency = checkPacks(existingPacks, pack.dependencies);
|
||||
if (!missingDependency.empty()) {
|
||||
throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'");
|
||||
}
|
||||
if (pack.dependencies.empty() || checkPacks(loadedPacks, pack.dependencies).empty()) {
|
||||
loadedPacks.insert(pack.id);
|
||||
resRoots.push_back(pack.folder);
|
||||
contentPacks.push_back(pack);
|
||||
ContentLoader loader(&pack);
|
||||
loader.load(contentBuilder);
|
||||
}
|
||||
}
|
||||
std::vector<PathsRoot> resRoots;
|
||||
for (auto& pack : contentPacks) {
|
||||
resRoots.push_back({pack.id, pack.folder});
|
||||
|
||||
ContentLoader loader(&pack);
|
||||
loader.load(contentBuilder);
|
||||
}
|
||||
|
||||
content.reset(contentBuilder.build());
|
||||
resPaths.reset(new ResPaths(resdir, resRoots));
|
||||
resPaths = std::make_unique<ResPaths>(resdir, resRoots);
|
||||
|
||||
Shader::preprocessor->setPaths(resPaths.get());
|
||||
langs::setup(resdir, langs::current->getId(), contentPacks);
|
||||
loadAssets();
|
||||
onAssetsLoaded();
|
||||
}
|
||||
|
||||
std::unique_ptr<Assets> new_assets(new Assets());
|
||||
std::cout << "-- loading assets" << std::endl;
|
||||
AssetsLoader loader(new_assets.get(), resPaths.get());
|
||||
AssetsLoader::addDefaults(loader, content.get());
|
||||
while (loader.hasNext()) {
|
||||
if (!loader.loadNext()) {
|
||||
new_assets.reset();
|
||||
throw std::runtime_error("could not to load assets");
|
||||
}
|
||||
}
|
||||
assets->extend(*new_assets.get());
|
||||
void Engine::resetContent() {
|
||||
auto manager = createPacksManager(fs::path());
|
||||
manager.scan();
|
||||
contentPacks = manager.getAll(basePacks);
|
||||
loadContent();
|
||||
}
|
||||
|
||||
void Engine::loadWorldContent(const fs::path& folder) {
|
||||
contentPacks.clear();
|
||||
auto packNames = ContentPack::worldPacksList(folder);
|
||||
ContentPack::readPacks(paths, contentPacks, packNames, folder);
|
||||
PacksManager manager;
|
||||
manager.setSources({
|
||||
folder/fs::path("content"),
|
||||
paths->getUserfiles()/fs::path("content"),
|
||||
paths->getResources()/fs::path("content")
|
||||
});
|
||||
manager.scan();
|
||||
contentPacks = manager.getAll(manager.assembly(packNames));
|
||||
paths->setWorldFolder(folder);
|
||||
loadContent();
|
||||
}
|
||||
|
||||
void Engine::loadAllPacks() {
|
||||
auto resdir = paths->getResources();
|
||||
contentPacks.clear();
|
||||
ContentPack::scan(paths, contentPacks);
|
||||
PacksManager manager = createPacksManager(paths->getWorldFolder());
|
||||
manager.scan();
|
||||
auto allnames = manager.getAllNames();
|
||||
contentPacks = manager.getAll(manager.assembly(allnames));
|
||||
}
|
||||
|
||||
double Engine::getDelta() const {
|
||||
@@ -263,9 +321,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
}
|
||||
|
||||
void Engine::setLanguage(std::string locale) {
|
||||
settings.ui.language = locale;
|
||||
langs::setup(paths->getResources(), locale, contentPacks);
|
||||
menus::create_menus(this);
|
||||
gui->getMenu()->setPageLoader(menus::create_page_loader(this));
|
||||
}
|
||||
|
||||
gui::GUI* Engine::getGUI() {
|
||||
@@ -288,6 +345,10 @@ std::vector<ContentPack>& Engine::getContentPacks() {
|
||||
return contentPacks;
|
||||
}
|
||||
|
||||
std::vector<std::string>& Engine::getBasePacks() {
|
||||
return basePacks;
|
||||
}
|
||||
|
||||
EnginePaths* Engine::getPaths() {
|
||||
return paths;
|
||||
}
|
||||
@@ -299,3 +360,12 @@ ResPaths* Engine::getResPaths() {
|
||||
std::shared_ptr<Screen> Engine::getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
void Engine::postRunnable(runnable callback) {
|
||||
std::lock_guard<std::recursive_mutex> lock(postRunnablesMutex);
|
||||
postRunnables.push(callback);
|
||||
}
|
||||
|
||||
SettingsHandler& Engine::getSettingsHandler() {
|
||||
return settingsHandler;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
#ifndef SRC_ENGINE_H_
|
||||
#define SRC_ENGINE_H_
|
||||
#ifndef ENGINE_HPP_
|
||||
#define ENGINE_HPP_
|
||||
|
||||
#include "delegates.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "content/Content.hpp"
|
||||
#include "content/ContentPack.hpp"
|
||||
#include "content/PacksManager.hpp"
|
||||
#include "files/engine_paths.hpp"
|
||||
#include "util/ObjectsKeeper.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
#include "typedefs.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "assets/Assets.h"
|
||||
#include "content/Content.h"
|
||||
#include "content/ContentPack.h"
|
||||
#include "files/engine_paths.h"
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
class Level;
|
||||
class Screen;
|
||||
class EnginePaths;
|
||||
class ResPaths;
|
||||
class Batch2D;
|
||||
class EngineController;
|
||||
class SettingsHandler;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -25,19 +34,30 @@ namespace gui {
|
||||
class GUI;
|
||||
}
|
||||
|
||||
namespace cmd {
|
||||
class CommandsInterpreter;
|
||||
}
|
||||
|
||||
class initialize_error : public std::runtime_error {
|
||||
public:
|
||||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
class Engine {
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
EngineSettings& settings;
|
||||
SettingsHandler& settingsHandler;
|
||||
EnginePaths* paths;
|
||||
|
||||
std::unique_ptr<Assets> assets = nullptr;
|
||||
std::shared_ptr<Screen> screen = nullptr;
|
||||
std::vector<ContentPack> contentPacks;
|
||||
EngineSettings& settings;
|
||||
std::unique_ptr<Content> content = nullptr;
|
||||
EnginePaths* paths;
|
||||
std::unique_ptr<ResPaths> resPaths = nullptr;
|
||||
std::queue<runnable> postRunnables;
|
||||
std::recursive_mutex postRunnablesMutex;
|
||||
std::unique_ptr<EngineController> controller;
|
||||
std::unique_ptr<cmd::CommandsInterpreter> interpreter;
|
||||
std::vector<std::string> basePacks {"base"};
|
||||
|
||||
uint64_t frame = 0;
|
||||
double lastTime = 0.0;
|
||||
@@ -45,16 +65,23 @@ class Engine {
|
||||
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
void updateTimers();
|
||||
void updateHotkeys();
|
||||
void renderFrame(Batch2D& batch);
|
||||
void processPostRunnables();
|
||||
void loadAssets();
|
||||
public:
|
||||
Engine(EngineSettings& settings, EnginePaths* paths);
|
||||
Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths);
|
||||
~Engine();
|
||||
|
||||
/// @brief Start main engine input/update/render loop.
|
||||
/// Automatically sets MenuScreen
|
||||
void mainloop();
|
||||
|
||||
/// @brief Called after assets loading when all engine systems are initialized
|
||||
void onAssetsLoaded();
|
||||
|
||||
/// @brief Set screen (scene).
|
||||
/// nullptr may be used to delete previous screen before creating new one,
|
||||
@@ -68,6 +95,8 @@ public:
|
||||
|
||||
/// @brief Load all selected content-packs and reload assets
|
||||
void loadContent();
|
||||
|
||||
void resetContent();
|
||||
|
||||
/// @brief Collect world content-packs and load content
|
||||
/// @see loadContent
|
||||
@@ -101,8 +130,22 @@ public:
|
||||
/// @brief Get selected content packs
|
||||
std::vector<ContentPack>& getContentPacks();
|
||||
|
||||
std::vector<std::string>& getBasePacks();
|
||||
|
||||
/// @brief Get current screen
|
||||
std::shared_ptr<Screen> getScreen();
|
||||
|
||||
/// @brief Enqueue function call to the end of current frame in draw thread
|
||||
void postRunnable(runnable callback);
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
EngineController* getController();
|
||||
cmd::CommandsInterpreter* getCommandsInterpreter();
|
||||
|
||||
PacksManager createPacksManager(const fs::path& worldFolder);
|
||||
|
||||
SettingsHandler& getSettingsHandler();
|
||||
};
|
||||
|
||||
#endif // SRC_ENGINE_H_
|
||||
#endif // ENGINE_HPP_
|
||||
+117
-45
@@ -1,30 +1,46 @@
|
||||
#include "WorldConverter.h"
|
||||
#include "WorldConverter.hpp"
|
||||
|
||||
#include "WorldFiles.hpp"
|
||||
|
||||
#include "../content/ContentLUT.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../debug/Logger.hpp"
|
||||
#include "../files/files.hpp"
|
||||
#include "../objects/Player.hpp"
|
||||
#include "../util/ThreadPool.hpp"
|
||||
#include "../voxels/Chunk.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include "WorldFiles.h"
|
||||
|
||||
#include "../data/dynamic.h"
|
||||
#include "../files/files.h"
|
||||
#include "../voxels/Chunk.h"
|
||||
#include "../content/ContentLUT.h"
|
||||
#include "../objects/Player.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static debug::Logger logger("world-converter");
|
||||
|
||||
class ConverterWorker : public util::Worker<convert_task, int> {
|
||||
std::shared_ptr<WorldConverter> converter;
|
||||
public:
|
||||
ConverterWorker(std::shared_ptr<WorldConverter> converter)
|
||||
: converter(converter) {}
|
||||
|
||||
int operator()(const std::shared_ptr<convert_task>& task) override {
|
||||
converter->convert(*task);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
WorldConverter::WorldConverter(
|
||||
fs::path folder,
|
||||
const Content* content,
|
||||
std::shared_ptr<ContentLUT> lut
|
||||
) : lut(lut), content(content)
|
||||
) : wfile(std::make_unique<WorldFiles>(folder)),
|
||||
lut(lut),
|
||||
content(content)
|
||||
{
|
||||
DebugSettings settings;
|
||||
wfile = new WorldFiles(folder, settings);
|
||||
|
||||
fs::path regionsFolder = wfile->getRegionsFolder();
|
||||
fs::path regionsFolder = wfile->getRegions().getRegionsFolder(REGION_LAYER_VOXELS);
|
||||
if (!fs::is_directory(regionsFolder)) {
|
||||
std::cerr << "nothing to convert" << std::endl;
|
||||
logger.error() << "nothing to convert";
|
||||
return;
|
||||
}
|
||||
tasks.push(convert_task {convert_task_type::player, wfile->getPlayerFile()});
|
||||
@@ -34,52 +50,68 @@ WorldConverter::WorldConverter(
|
||||
}
|
||||
|
||||
WorldConverter::~WorldConverter() {
|
||||
delete wfile;
|
||||
}
|
||||
|
||||
bool WorldConverter::hasNext() const {
|
||||
return !tasks.empty();
|
||||
std::shared_ptr<Task> WorldConverter::startTask(
|
||||
fs::path folder,
|
||||
const Content* content,
|
||||
std::shared_ptr<ContentLUT> lut,
|
||||
runnable onDone,
|
||||
bool multithreading
|
||||
) {
|
||||
auto converter = std::make_shared<WorldConverter>(folder, content, lut);
|
||||
if (!multithreading) {
|
||||
converter->setOnComplete([=]() {
|
||||
converter->write();
|
||||
onDone();
|
||||
});
|
||||
return converter;
|
||||
}
|
||||
auto pool = std::make_shared<util::ThreadPool<convert_task, int>>(
|
||||
"converter-pool",
|
||||
[=](){return std::make_shared<ConverterWorker>(converter);},
|
||||
[=](int&) {}
|
||||
);
|
||||
while (!converter->tasks.empty()) {
|
||||
const convert_task& task = converter->tasks.front();
|
||||
auto ptr = std::make_shared<convert_task>(task);
|
||||
pool->enqueueJob(ptr);
|
||||
converter->tasks.pop();
|
||||
}
|
||||
pool->setOnComplete([=]() {
|
||||
converter->write();
|
||||
onDone();
|
||||
});
|
||||
return pool;
|
||||
}
|
||||
|
||||
void WorldConverter::convertRegion(fs::path file) {
|
||||
void WorldConverter::convertRegion(fs::path file) const {
|
||||
int x, z;
|
||||
std::string name = file.stem().string();
|
||||
if (!WorldFiles::parseRegionFilename(name, x, z)) {
|
||||
std::cerr << "could not parse name " << name << std::endl;
|
||||
if (!WorldRegions::parseRegionFilename(name, x, z)) {
|
||||
logger.error() << "could not parse name " << name;
|
||||
return;
|
||||
}
|
||||
std::cout << "converting region " << name << std::endl;
|
||||
for (uint cz = 0; cz < REGION_SIZE; cz++) {
|
||||
for (uint cx = 0; cx < REGION_SIZE; cx++) {
|
||||
int gx = cx + x * REGION_SIZE;
|
||||
int gz = cz + z * REGION_SIZE;
|
||||
std::unique_ptr<ubyte[]> data (wfile->getChunk(gx, gz));
|
||||
if (data == nullptr)
|
||||
continue;
|
||||
if (lut) {
|
||||
Chunk::convert(data.get(), lut.get());
|
||||
}
|
||||
wfile->put(gx, gz, data.get());
|
||||
logger.info() << "converting region " << name;
|
||||
wfile->getRegions().processRegionVoxels(x, z, [=](ubyte* data) {
|
||||
if (lut) {
|
||||
Chunk::convert(data, lut.get());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void WorldConverter::convertPlayer(fs::path file) {
|
||||
std::cout << "converting player " << file.u8string() << std::endl;
|
||||
void WorldConverter::convertPlayer(fs::path file) const {
|
||||
logger.info() << "converting player " << file.u8string();
|
||||
auto map = files::read_json(file);
|
||||
Player::convert(map.get(), lut.get());
|
||||
files::write_json(file, map.get());
|
||||
}
|
||||
|
||||
void WorldConverter::convertNext() {
|
||||
if (!hasNext()) {
|
||||
throw std::runtime_error("no more regions to convert");
|
||||
}
|
||||
convert_task task = tasks.front();
|
||||
tasks.pop();
|
||||
|
||||
void WorldConverter::convert(convert_task task) const {
|
||||
if (!fs::is_regular_file(task.file))
|
||||
return;
|
||||
|
||||
switch (task.type) {
|
||||
case convert_task_type::region:
|
||||
convertRegion(task.file);
|
||||
@@ -90,11 +122,51 @@ void WorldConverter::convertNext() {
|
||||
}
|
||||
}
|
||||
|
||||
void WorldConverter::convertNext() {
|
||||
if (tasks.empty()) {
|
||||
throw std::runtime_error("no more regions to convert");
|
||||
}
|
||||
convert_task task = tasks.front();
|
||||
tasks.pop();
|
||||
tasksDone++;
|
||||
|
||||
convert(task);
|
||||
}
|
||||
|
||||
void WorldConverter::setOnComplete(runnable callback) {
|
||||
this->onComplete = callback;
|
||||
}
|
||||
|
||||
void WorldConverter::update() {
|
||||
convertNext();
|
||||
if (onComplete && tasks.empty()) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldConverter::terminate() {
|
||||
tasks = {};
|
||||
}
|
||||
|
||||
bool WorldConverter::isActive() const {
|
||||
return !tasks.empty();
|
||||
}
|
||||
|
||||
void WorldConverter::write() {
|
||||
std::cout << "writing world" << std::endl;
|
||||
logger.info() << "writing world";
|
||||
wfile->write(nullptr, content);
|
||||
}
|
||||
|
||||
uint WorldConverter::getTotalTasks() const {
|
||||
return tasks.size();
|
||||
void WorldConverter::waitForEnd() {
|
||||
while (isActive()) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
uint WorldConverter::getWorkTotal() const {
|
||||
return tasks.size() + tasksDone;
|
||||
}
|
||||
|
||||
uint WorldConverter::getWorkDone() const {
|
||||
return tasksDone;
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef FILES_WORLD_CONVERTER_H_
|
||||
#define FILES_WORLD_CONVERTER_H_
|
||||
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include "../typedefs.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class Content;
|
||||
class ContentLUT;
|
||||
class WorldFiles;
|
||||
|
||||
enum class convert_task_type {
|
||||
region, player
|
||||
};
|
||||
|
||||
struct convert_task {
|
||||
convert_task_type type;
|
||||
fs::path file;
|
||||
};
|
||||
|
||||
class WorldConverter {
|
||||
WorldFiles* wfile;
|
||||
std::shared_ptr<ContentLUT> const lut;
|
||||
const Content* const content;
|
||||
std::queue<convert_task> tasks;
|
||||
|
||||
void convertPlayer(fs::path file);
|
||||
void convertRegion(fs::path file);
|
||||
public:
|
||||
WorldConverter(fs::path folder, const Content* content,
|
||||
std::shared_ptr<ContentLUT> lut);
|
||||
~WorldConverter();
|
||||
|
||||
bool hasNext() const;
|
||||
void convertNext();
|
||||
|
||||
void write();
|
||||
|
||||
uint getTotalTasks() const;
|
||||
};
|
||||
|
||||
#endif // FILES_WORLD_CONVERTER_H_
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef FILES_WORLD_CONVERTER_HPP_
|
||||
#define FILES_WORLD_CONVERTER_HPP_
|
||||
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
#include "../delegates.hpp"
|
||||
#include "../interfaces/Task.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class Content;
|
||||
class ContentLUT;
|
||||
class WorldFiles;
|
||||
|
||||
enum class convert_task_type {
|
||||
region, player
|
||||
};
|
||||
|
||||
struct convert_task {
|
||||
convert_task_type type;
|
||||
fs::path file;
|
||||
};
|
||||
|
||||
class WorldConverter : public Task {
|
||||
std::unique_ptr<WorldFiles> wfile;
|
||||
std::shared_ptr<ContentLUT> const lut;
|
||||
const Content* const content;
|
||||
std::queue<convert_task> tasks;
|
||||
runnable onComplete;
|
||||
uint tasksDone = 0;
|
||||
|
||||
void convertPlayer(fs::path file) const;
|
||||
void convertRegion(fs::path file) const;
|
||||
public:
|
||||
WorldConverter(
|
||||
fs::path folder,
|
||||
const Content* content,
|
||||
std::shared_ptr<ContentLUT> lut
|
||||
);
|
||||
~WorldConverter();
|
||||
|
||||
void convert(convert_task task) const;
|
||||
void convertNext();
|
||||
void setOnComplete(runnable callback);
|
||||
void write();
|
||||
|
||||
void update() override;
|
||||
void terminate() override;
|
||||
bool isActive() const override;
|
||||
void waitForEnd() override;
|
||||
uint getWorkTotal() const override;
|
||||
uint getWorkDone() const override;
|
||||
|
||||
static std::shared_ptr<Task> startTask(
|
||||
fs::path folder,
|
||||
const Content* content,
|
||||
std::shared_ptr<ContentLUT> lut,
|
||||
runnable onDone,
|
||||
bool multithreading
|
||||
);
|
||||
};
|
||||
|
||||
#endif // FILES_WORLD_CONVERTER_HPP_
|
||||
+46
-509
@@ -1,26 +1,24 @@
|
||||
#include "WorldFiles.h"
|
||||
#include "WorldFiles.hpp"
|
||||
|
||||
#include "rle.h"
|
||||
#include "../window/Camera.h"
|
||||
#include "../content/Content.h"
|
||||
#include "../objects/Player.h"
|
||||
#include "../physics/Hitbox.h"
|
||||
#include "../voxels/voxel.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../voxels/Chunk.h"
|
||||
#include "../typedefs.h"
|
||||
#include "../maths/voxmaths.h"
|
||||
#include "../world/World.h"
|
||||
#include "../lighting/Lightmap.h"
|
||||
|
||||
#include "../coders/byte_utils.h"
|
||||
#include "../util/data_io.h"
|
||||
#include "../coders/json.h"
|
||||
#include "../constants.h"
|
||||
#include "../items/ItemDef.h"
|
||||
#include "../items/Inventory.h"
|
||||
|
||||
#include "../data/dynamic.h"
|
||||
#include "../coders/byte_utils.hpp"
|
||||
#include "../coders/json.hpp"
|
||||
#include "../constants.hpp"
|
||||
#include "../content/Content.hpp"
|
||||
#include "../core_defs.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../items/Inventory.hpp"
|
||||
#include "../items/ItemDef.hpp"
|
||||
#include "../lighting/Lightmap.hpp"
|
||||
#include "../maths/voxmaths.hpp"
|
||||
#include "../objects/Player.hpp"
|
||||
#include "../physics/Hitbox.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
#include "../util/data_io.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
#include "../voxels/Chunk.hpp"
|
||||
#include "../voxels/voxel.hpp"
|
||||
#include "../window/Camera.hpp"
|
||||
#include "../world/World.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
@@ -29,77 +27,18 @@
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#define REGION_FORMAT_MAGIC ".VOXREG"
|
||||
#define WORLD_FORMAT_MAGIC ".VOXWLD"
|
||||
|
||||
const size_t BUFFER_SIZE_UNKNOWN = -1;
|
||||
|
||||
regfile::regfile(fs::path filename) : file(filename) {
|
||||
if (file.length() < REGION_HEADER_SIZE)
|
||||
throw std::runtime_error("incomplete region file header");
|
||||
char header[REGION_HEADER_SIZE];
|
||||
file.read(header, REGION_HEADER_SIZE);
|
||||
|
||||
// avoid of use strcmp_s
|
||||
if (std::string(header, strlen(REGION_FORMAT_MAGIC)) != REGION_FORMAT_MAGIC) {
|
||||
throw std::runtime_error("invalid region file magic number");
|
||||
}
|
||||
version = header[8];
|
||||
if (uint(version) > REGION_FORMAT_VERSION) {
|
||||
throw illegal_region_format(
|
||||
"region format "+std::to_string(version)+" is not supported");
|
||||
}
|
||||
WorldFiles::WorldFiles(fs::path directory) : directory(directory), regions(directory) {
|
||||
}
|
||||
|
||||
WorldRegion::WorldRegion() {
|
||||
chunksData = new ubyte*[REGION_CHUNKS_COUNT]{};
|
||||
sizes = new uint32_t[REGION_CHUNKS_COUNT]{};
|
||||
}
|
||||
|
||||
WorldRegion::~WorldRegion() {
|
||||
for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
delete[] chunksData[i];
|
||||
}
|
||||
delete[] sizes;
|
||||
delete[] chunksData;
|
||||
}
|
||||
|
||||
void WorldRegion::setUnsaved(bool unsaved) {
|
||||
this->unsaved = unsaved;
|
||||
}
|
||||
bool WorldRegion::isUnsaved() const {
|
||||
return unsaved;
|
||||
}
|
||||
|
||||
ubyte** WorldRegion::getChunks() const {
|
||||
return chunksData;
|
||||
}
|
||||
|
||||
uint32_t* WorldRegion::getSizes() const {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
void WorldRegion::put(uint x, uint z, ubyte* data, uint32_t size) {
|
||||
size_t chunk_index = z * REGION_SIZE + x;
|
||||
delete[] chunksData[chunk_index];
|
||||
chunksData[chunk_index] = data;
|
||||
sizes[chunk_index] = size;
|
||||
}
|
||||
|
||||
ubyte* WorldRegion::getChunkData(uint x, uint z) {
|
||||
return chunksData[z * REGION_SIZE + x];
|
||||
}
|
||||
|
||||
uint WorldRegion::getChunkDataSize(uint x, uint z) {
|
||||
return sizes[z * REGION_SIZE + x];
|
||||
}
|
||||
|
||||
WorldFiles::WorldFiles(fs::path directory, const DebugSettings& settings)
|
||||
: directory(directory),
|
||||
generatorTestMode(settings.generatorTestMode),
|
||||
doWriteLights(settings.doWriteLights)
|
||||
WorldFiles::WorldFiles(fs::path directory, const DebugSettings& settings)
|
||||
: WorldFiles(directory)
|
||||
{
|
||||
compressionBuffer = std::make_unique<ubyte[]>(CHUNK_DATA_LEN * 2);
|
||||
generatorTestMode = settings.generatorTestMode.get();
|
||||
doWriteLights = settings.doWriteLights.get();
|
||||
regions.generatorTestMode = generatorTestMode;
|
||||
regions.doWriteLights = doWriteLights;
|
||||
}
|
||||
|
||||
WorldFiles::~WorldFiles() {
|
||||
@@ -110,172 +49,6 @@ void WorldFiles::createDirectories() {
|
||||
fs::create_directories(directory / fs::path("content"));
|
||||
}
|
||||
|
||||
WorldRegion* WorldFiles::getRegion(regionsmap& regions, int x, int z) {
|
||||
auto found = regions.find(glm::ivec2(x, z));
|
||||
if (found == regions.end())
|
||||
return nullptr;
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
WorldRegion* WorldFiles::getOrCreateRegion(regionsmap& regions, int x, int z) {
|
||||
WorldRegion* region = getRegion(regions, x, z);
|
||||
if (region == nullptr) {
|
||||
region = new WorldRegion();
|
||||
regions[glm::ivec2(x, z)].reset(region);
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
ubyte* WorldFiles::compress(const ubyte* src, size_t srclen, size_t& len) {
|
||||
ubyte* buffer = this->compressionBuffer.get();
|
||||
|
||||
len = extrle::encode(src, srclen, buffer);
|
||||
ubyte* data = new ubyte[len];
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data[i] = buffer[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
ubyte* WorldFiles::decompress(const ubyte* src, size_t srclen, size_t dstlen) {
|
||||
ubyte* decompressed = new ubyte[dstlen];
|
||||
extrle::decode(src, srclen, decompressed);
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
int WorldFiles::getVoxelRegionVersion(int x, int z) {
|
||||
regfile* rf = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS), getRegionsFolder());
|
||||
if (rf == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return rf->version;
|
||||
}
|
||||
|
||||
int WorldFiles::getVoxelRegionsVersion() {
|
||||
fs::path regionsFolder = getRegionsFolder();
|
||||
if (!fs::is_directory(regionsFolder)) {
|
||||
return REGION_FORMAT_VERSION;
|
||||
}
|
||||
for (auto file : fs::directory_iterator(regionsFolder)) {
|
||||
int x;
|
||||
int z;
|
||||
if (!parseRegionFilename(file.path().stem().string(), x, z)) {
|
||||
continue;
|
||||
}
|
||||
regfile* rf = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS), regionsFolder);
|
||||
return rf->version;
|
||||
}
|
||||
return REGION_FORMAT_VERSION;
|
||||
}
|
||||
|
||||
|
||||
/// @brief Compress and store chunk voxels data in region
|
||||
/// @param x chunk.x
|
||||
/// @param z chunk.z
|
||||
void WorldFiles::put(int x, int z, const ubyte* voxelData) {
|
||||
int regionX = floordiv(x, REGION_SIZE);
|
||||
int regionZ = floordiv(z, REGION_SIZE);
|
||||
int localX = x - (regionX * REGION_SIZE);
|
||||
int localZ = z - (regionZ * REGION_SIZE);
|
||||
|
||||
/* Writing Voxels */ {
|
||||
WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ);
|
||||
region->setUnsaved(true);
|
||||
size_t compressedSize;
|
||||
ubyte* data = compress(voxelData, CHUNK_DATA_LEN, compressedSize);
|
||||
region->put(localX, localZ, data, compressedSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Store chunk (voxels and lights) in region (existing or new)
|
||||
void WorldFiles::put(Chunk* chunk){
|
||||
assert(chunk != nullptr);
|
||||
|
||||
int regionX = floordiv(chunk->x, REGION_SIZE);
|
||||
int regionZ = floordiv(chunk->z, REGION_SIZE);
|
||||
int localX = chunk->x - (regionX * REGION_SIZE);
|
||||
int localZ = chunk->z - (regionZ * REGION_SIZE);
|
||||
|
||||
/* Writing voxels */ {
|
||||
size_t compressedSize;
|
||||
std::unique_ptr<ubyte[]> chunk_data (chunk->encode());
|
||||
ubyte* data = compress(chunk_data.get(), CHUNK_DATA_LEN, compressedSize);
|
||||
|
||||
WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ);
|
||||
region->setUnsaved(true);
|
||||
region->put(localX, localZ, data, compressedSize);
|
||||
}
|
||||
// Writing lights cache
|
||||
if (doWriteLights && chunk->isLighted()) {
|
||||
size_t compressedSize;
|
||||
std::unique_ptr<ubyte[]> light_data (chunk->lightmap.encode());
|
||||
ubyte* data = compress(light_data.get(), LIGHTMAP_DATA_LEN, compressedSize);
|
||||
|
||||
WorldRegion* region = getOrCreateRegion(lights, regionX, regionZ);
|
||||
region->setUnsaved(true);
|
||||
region->put(localX, localZ, data, compressedSize);
|
||||
}
|
||||
// Writing block inventories
|
||||
if (!chunk->inventories.empty()){
|
||||
auto& inventories = chunk->inventories;
|
||||
ByteBuilder builder;
|
||||
builder.putInt32(inventories.size());
|
||||
for (auto& entry : inventories) {
|
||||
builder.putInt32(entry.first);
|
||||
auto map = entry.second->serialize();
|
||||
auto bytes = json::to_binary(map.get(), true);
|
||||
builder.putInt32(bytes.size());
|
||||
builder.put(bytes.data(), bytes.size());
|
||||
}
|
||||
WorldRegion* region = getOrCreateRegion(storages, regionX, regionZ);
|
||||
region->setUnsaved(true);
|
||||
|
||||
auto datavec = builder.data();
|
||||
uint datasize = builder.size();
|
||||
auto data = std::make_unique<ubyte[]>(datasize);
|
||||
for (uint i = 0; i < datasize; i++) {
|
||||
data[i] = datavec[i];
|
||||
}
|
||||
region->put(localX, localZ, data.release(), datasize);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getRegionsFolder() const {
|
||||
return directory/fs::path("regions");
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getLightsFolder() const {
|
||||
return directory/fs::path("lights");
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getInventoriesFolder() const {
|
||||
return directory/fs::path("inventories");
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getRegionFilename(int x, int z) const {
|
||||
return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin");
|
||||
}
|
||||
|
||||
/// @brief Extract X and Z from 'X_Z.bin' region file name.
|
||||
/// @param name source region file name
|
||||
/// @param x parsed X destination
|
||||
/// @param z parsed Z destination
|
||||
/// @return false if std::invalid_argument or std::out_of_range occurred
|
||||
bool WorldFiles::parseRegionFilename(const std::string& name, int& x, int& z) {
|
||||
size_t sep = name.find('_');
|
||||
if (sep == std::string::npos || sep == 0 || sep == name.length()-1)
|
||||
return false;
|
||||
try {
|
||||
x = std::stoi(name.substr(0, sep));
|
||||
z = std::stoi(name.substr(sep+1));
|
||||
} catch (std::invalid_argument& err) {
|
||||
return false;
|
||||
} catch (std::out_of_range& err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getPlayerFile() const {
|
||||
return directory/fs::path("player.json");
|
||||
}
|
||||
@@ -292,229 +65,23 @@ fs::path WorldFiles::getPacksFile() const {
|
||||
return directory/fs::path("packs.list");
|
||||
}
|
||||
|
||||
ubyte* WorldFiles::getChunk(int x, int z){
|
||||
return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true);
|
||||
}
|
||||
|
||||
/// @brief Get cached lights for chunk at x,z
|
||||
/// @return lights data or nullptr
|
||||
light_t* WorldFiles::getLights(int x, int z) {
|
||||
std::unique_ptr<ubyte[]> data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true));
|
||||
if (data == nullptr)
|
||||
return nullptr;
|
||||
return Lightmap::decode(data.get());
|
||||
}
|
||||
|
||||
chunk_inventories_map WorldFiles::fetchInventories(int x, int z) {
|
||||
chunk_inventories_map inventories;
|
||||
const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false);
|
||||
if (data == nullptr)
|
||||
return inventories;
|
||||
ByteReader reader(data, BUFFER_SIZE_UNKNOWN);
|
||||
int count = reader.getInt32();
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint index = reader.getInt32();
|
||||
uint size = reader.getInt32();
|
||||
auto map = json::from_binary(reader.pointer(), size);
|
||||
reader.skip(size);
|
||||
auto inv = std::make_shared<Inventory>(0, 0);
|
||||
inv->deserialize(map.get());
|
||||
inventories[index] = inv;
|
||||
}
|
||||
return inventories;
|
||||
}
|
||||
|
||||
ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder,
|
||||
int x, int z, int layer, bool compression) {
|
||||
int regionX = floordiv(x, REGION_SIZE);
|
||||
int regionZ = floordiv(z, REGION_SIZE);
|
||||
|
||||
int localX = x - (regionX * REGION_SIZE);
|
||||
int localZ = z - (regionZ * REGION_SIZE);
|
||||
|
||||
WorldRegion* region = getOrCreateRegion(regions, regionX, regionZ);
|
||||
ubyte* data = region->getChunkData(localX, localZ);
|
||||
if (data == nullptr) {
|
||||
uint32_t size;
|
||||
data = readChunkData(x, z, size, folder, layer);
|
||||
if (data != nullptr) {
|
||||
region->put(localX, localZ, data, size);
|
||||
}
|
||||
}
|
||||
if (data != nullptr) {
|
||||
size_t size = region->getChunkDataSize(localX, localZ);
|
||||
if (compression) {
|
||||
return decompress(data, size, CHUNK_DATA_LEN);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
regfile* WorldFiles::getRegFile(glm::ivec3 coord, const fs::path& folder) {
|
||||
const auto found = openRegFiles.find(coord);
|
||||
if (found != openRegFiles.end()) {
|
||||
return found->second.get();
|
||||
}
|
||||
if (openRegFiles.size() == MAX_OPEN_REGION_FILES) {
|
||||
// [todo] replace with closing the most unused region
|
||||
auto iter = std::next(openRegFiles.begin(), rand() % openRegFiles.size());
|
||||
openRegFiles.erase(iter);
|
||||
}
|
||||
fs::path filename = folder / getRegionFilename(coord[0], coord[1]);
|
||||
if (!fs::is_regular_file(filename)) {
|
||||
return nullptr;
|
||||
}
|
||||
openRegFiles[coord] = std::make_unique<regfile>(filename);
|
||||
return openRegFiles[coord].get();
|
||||
}
|
||||
|
||||
ubyte* WorldFiles::readChunkData(int x,
|
||||
int z,
|
||||
uint32_t& length,
|
||||
fs::path folder,
|
||||
int layer){
|
||||
if (generatorTestMode)
|
||||
return nullptr;
|
||||
|
||||
int regionX = floordiv(x, REGION_SIZE);
|
||||
int regionZ = floordiv(z, REGION_SIZE);
|
||||
int localX = x - (regionX * REGION_SIZE);
|
||||
int localZ = z - (regionZ * REGION_SIZE);
|
||||
int chunkIndex = localZ * REGION_SIZE + localX;
|
||||
|
||||
glm::ivec3 coord(regionX, regionZ, layer);
|
||||
regfile* rfile = WorldFiles::getRegFile(coord, folder);
|
||||
if (rfile == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
files::rafile& file = rfile->file;
|
||||
|
||||
size_t file_size = file.length();
|
||||
size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4;
|
||||
|
||||
uint32_t offset;
|
||||
file.seekg(table_offset + chunkIndex * 4);
|
||||
file.read((char*)(&offset), 4);
|
||||
offset = dataio::read_int32_big((const ubyte*)(&offset), 0);
|
||||
|
||||
if (offset == 0){
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
file.seekg(offset);
|
||||
file.read((char*)(&offset), 4);
|
||||
length = dataio::read_int32_big((const ubyte*)(&offset), 0);
|
||||
ubyte* data = new ubyte[length]{};
|
||||
file.read((char*)data, length);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// @brief Read missing chunks data (null pointers) from region file
|
||||
/// @param layer used as third part of openRegFiles map key
|
||||
/// (see REGION_LAYER_* constants)
|
||||
void WorldFiles::fetchChunks(WorldRegion* region, int x, int z, fs::path folder, int layer) {
|
||||
ubyte** chunks = region->getChunks();
|
||||
uint32_t* sizes = region->getSizes();
|
||||
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE;
|
||||
int chunk_z = (i / REGION_SIZE) + z * REGION_SIZE;
|
||||
if (chunks[i] == nullptr) {
|
||||
chunks[i] = readChunkData(chunk_x, chunk_z, sizes[i], folder, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Write or rewrite region file
|
||||
/// @param x region X
|
||||
/// @param z region Z
|
||||
/// @param layer used as third part of openRegFiles map key
|
||||
/// (see REGION_LAYER_* constants)
|
||||
void WorldFiles::writeRegion(int x, int z, WorldRegion* entry, fs::path folder, int layer){
|
||||
fs::path filename = folder/getRegionFilename(x, z);
|
||||
|
||||
glm::ivec3 regcoord(x, z, layer);
|
||||
if (getRegFile(regcoord, folder)) {
|
||||
fetchChunks(entry, x, z, folder, layer);
|
||||
openRegFiles.erase(regcoord);
|
||||
}
|
||||
|
||||
char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC;
|
||||
header[8] = REGION_FORMAT_VERSION;
|
||||
header[9] = 0; // flags
|
||||
std::ofstream file(filename, std::ios::out | std::ios::binary);
|
||||
file.write(header, REGION_HEADER_SIZE);
|
||||
|
||||
size_t offset = REGION_HEADER_SIZE;
|
||||
char intbuf[4]{};
|
||||
uint offsets[REGION_CHUNKS_COUNT]{};
|
||||
|
||||
ubyte** region = entry->getChunks();
|
||||
uint32_t* sizes = entry->getSizes();
|
||||
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
ubyte* chunk = region[i];
|
||||
if (chunk == nullptr){
|
||||
offsets[i] = 0;
|
||||
} else {
|
||||
offsets[i] = offset;
|
||||
|
||||
size_t compressedSize = sizes[i];
|
||||
dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0);
|
||||
offset += 4 + compressedSize;
|
||||
|
||||
file.write(intbuf, 4);
|
||||
file.write((const char*)chunk, compressedSize);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0);
|
||||
file.write(intbuf, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldFiles::writeRegions(regionsmap& regions, const fs::path& folder, int layer) {
|
||||
for (auto& it : regions){
|
||||
WorldRegion* region = it.second.get();
|
||||
if (region->getChunks() == nullptr || !region->isUnsaved())
|
||||
continue;
|
||||
glm::ivec2 key = it.first;
|
||||
writeRegion(key[0], key[1], region, folder, layer);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldFiles::write(const World* world, const Content* content) {
|
||||
fs::path regionsFolder = getRegionsFolder();
|
||||
fs::path lightsFolder = getLightsFolder();
|
||||
fs::path inventoriesFolder = getInventoriesFolder();
|
||||
|
||||
fs::create_directories(regionsFolder);
|
||||
fs::create_directories(inventoriesFolder);
|
||||
fs::create_directories(lightsFolder);
|
||||
|
||||
if (world) {
|
||||
writeWorldInfo(world);
|
||||
writePacks(world);
|
||||
if (!fs::exists(getPacksFile())) {
|
||||
writePacks(world->getPacks());
|
||||
}
|
||||
}
|
||||
if (generatorTestMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeIndices(content->getIndices());
|
||||
writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS);
|
||||
writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS);
|
||||
writeRegions(storages, inventoriesFolder, REGION_LAYER_INVENTORIES);
|
||||
regions.write();
|
||||
}
|
||||
|
||||
void WorldFiles::writePacks(const World* world) {
|
||||
void WorldFiles::writePacks(const std::vector<ContentPack>& packs) {
|
||||
auto packsFile = getPacksFile();
|
||||
if (fs::is_regular_file(packsFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& packs = world->getPacks();
|
||||
std::stringstream ss;
|
||||
ss << "# autogenerated; do not modify\n";
|
||||
for (const auto& pack : packs) {
|
||||
@@ -559,56 +126,15 @@ bool WorldFiles::readWorldInfo(World* world) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldFiles::addPack(const World* world, const std::string& id) {
|
||||
fs::path file = getPacksFile();
|
||||
if (!fs::is_regular_file(file)) {
|
||||
if (!fs::is_directory(directory)) {
|
||||
fs::create_directories(directory);
|
||||
}
|
||||
writePacks(world);
|
||||
}
|
||||
auto packs = files::read_list(file);
|
||||
packs.push_back(id);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "# autogenerated; do not modify\n";
|
||||
for (const auto& pack : packs) {
|
||||
ss << pack << "\n";
|
||||
}
|
||||
files::write_string(file, ss.str());
|
||||
}
|
||||
|
||||
void WorldFiles::removePack(const World* world, const std::string& id) {
|
||||
fs::path file = getPacksFile();
|
||||
if (!fs::is_regular_file(file)) {
|
||||
if (!fs::is_directory(directory)) {
|
||||
fs::create_directories(directory);
|
||||
}
|
||||
writePacks(world);
|
||||
}
|
||||
auto packs = files::read_list(file);
|
||||
auto found = std::find(packs.begin(), packs.end(), id);
|
||||
if (found != packs.end()) {
|
||||
packs.erase(found);
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "# autogenerated; do not modify\n";
|
||||
for (const auto& pack : packs) {
|
||||
ss << pack << "\n";
|
||||
}
|
||||
files::write_string(file, ss.str());
|
||||
|
||||
// erase invalid indices
|
||||
static void erase_pack_indices(dynamic::Map* root, const std::string& id) {
|
||||
auto prefix = id+":";
|
||||
auto root = files::read_json(getIndicesFile());
|
||||
auto blocks = root->list("blocks");
|
||||
for (uint i = 0; i < blocks->size(); i++) {
|
||||
auto name = blocks->str(i);
|
||||
if (name.find(prefix) != 0)
|
||||
continue;
|
||||
auto value = blocks->getValueWriteable(i);
|
||||
*value->value.str = "core:air";
|
||||
*value = CORE_AIR;
|
||||
}
|
||||
|
||||
auto items = root->list("items");
|
||||
@@ -617,7 +143,18 @@ void WorldFiles::removePack(const World* world, const std::string& id) {
|
||||
if (name.find(prefix) != 0)
|
||||
continue;
|
||||
auto value = items->getValueWriteable(i);
|
||||
*value->value.str = "core:empty";
|
||||
*value = CORE_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldFiles::removeIndices(const std::vector<std::string>& packs) {
|
||||
auto root = files::read_json(getIndicesFile());
|
||||
for (const auto& id : packs) {
|
||||
erase_pack_indices(root.get(), id);
|
||||
}
|
||||
files::write_json(getIndicesFile(), root.get());
|
||||
}
|
||||
|
||||
fs::path WorldFiles::getFolder() const {
|
||||
return directory;
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
#ifndef FILES_WORLDFILES_H_
|
||||
#define FILES_WORLDFILES_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include "glm/gtx/hash.hpp"
|
||||
|
||||
#include "files.h"
|
||||
#include "../typedefs.h"
|
||||
#include "../settings.h"
|
||||
|
||||
#include "../voxels/Chunk.h"
|
||||
|
||||
inline constexpr uint REGION_HEADER_SIZE = 10;
|
||||
|
||||
inline constexpr uint REGION_LAYER_VOXELS = 0;
|
||||
inline constexpr uint REGION_LAYER_LIGHTS = 1;
|
||||
inline constexpr uint REGION_LAYER_INVENTORIES = 2;
|
||||
|
||||
inline constexpr uint REGION_SIZE_BIT = 5;
|
||||
inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT));
|
||||
inline constexpr uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE));
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 2;
|
||||
inline constexpr uint WORLD_FORMAT_VERSION = 1;
|
||||
inline constexpr uint MAX_OPEN_REGION_FILES = 16;
|
||||
|
||||
class Player;
|
||||
class Content;
|
||||
class ContentIndices;
|
||||
class World;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class illegal_region_format : public std::runtime_error {
|
||||
public:
|
||||
illegal_region_format(const std::string& message)
|
||||
: std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
class WorldRegion {
|
||||
ubyte** chunksData;
|
||||
uint32_t* sizes;
|
||||
bool unsaved = false;
|
||||
public:
|
||||
WorldRegion();
|
||||
~WorldRegion();
|
||||
|
||||
void put(uint x, uint z, ubyte* data, uint32_t size);
|
||||
ubyte* getChunkData(uint x, uint z);
|
||||
uint getChunkDataSize(uint x, uint z);
|
||||
|
||||
void setUnsaved(bool unsaved);
|
||||
bool isUnsaved() const;
|
||||
|
||||
ubyte** getChunks() const;
|
||||
uint32_t* getSizes() const;
|
||||
};
|
||||
|
||||
struct regfile {
|
||||
files::rafile file;
|
||||
int version;
|
||||
|
||||
regfile(fs::path filename);
|
||||
};
|
||||
|
||||
typedef std::unordered_map<glm::ivec2, std::unique_ptr<WorldRegion>> regionsmap;
|
||||
|
||||
class WorldFiles {
|
||||
std::unordered_map<glm::ivec3, std::unique_ptr<regfile>> openRegFiles;
|
||||
|
||||
void writeWorldInfo(const World* world);
|
||||
fs::path getRegionFilename(int x, int y) const;
|
||||
fs::path getWorldFile() const;
|
||||
fs::path getIndicesFile() const;
|
||||
fs::path getPacksFile() const;
|
||||
|
||||
WorldRegion* getRegion(regionsmap& regions, int x, int z);
|
||||
WorldRegion* getOrCreateRegion(regionsmap& regions, int x, int z);
|
||||
|
||||
/// @brief Compress buffer with extrle
|
||||
/// @param src source buffer
|
||||
/// @param srclen length of the source buffer
|
||||
/// @param len (out argument) length of result buffer
|
||||
/// @return compressed bytes array
|
||||
ubyte* compress(const ubyte* src, size_t srclen, size_t& len);
|
||||
|
||||
/// @brief Decompress buffer with extrle
|
||||
/// @param src compressed buffer
|
||||
/// @param srclen length of compressed buffer
|
||||
/// @param dstlen max expected length of source buffer
|
||||
/// @return decompressed bytes array
|
||||
ubyte* decompress(const ubyte* src, size_t srclen, size_t dstlen);
|
||||
|
||||
ubyte* readChunkData(int x, int y, uint32_t& length, fs::path folder, int layer);
|
||||
|
||||
void fetchChunks(WorldRegion* region, int x, int y, fs::path folder, int layer);
|
||||
|
||||
void writeRegions(regionsmap& regions, const fs::path& folder, int layer);
|
||||
|
||||
ubyte* getData(regionsmap& regions, const fs::path& folder, int x, int z, int layer, bool compression);
|
||||
|
||||
regfile* getRegFile(glm::ivec3 coord, const fs::path& folder);
|
||||
|
||||
fs::path getLightsFolder() const;
|
||||
fs::path getInventoriesFolder() const;
|
||||
public:
|
||||
static bool parseRegionFilename(const std::string& name, int& x, int& y);
|
||||
fs::path getRegionsFolder() const;
|
||||
fs::path getPlayerFile() const;
|
||||
|
||||
regionsmap regions;
|
||||
regionsmap storages;
|
||||
regionsmap lights;
|
||||
fs::path directory;
|
||||
std::unique_ptr<ubyte[]> compressionBuffer;
|
||||
bool generatorTestMode;
|
||||
bool doWriteLights;
|
||||
|
||||
WorldFiles(fs::path directory, const DebugSettings& settings);
|
||||
~WorldFiles();
|
||||
|
||||
void createDirectories();
|
||||
|
||||
void put(Chunk* chunk);
|
||||
void put(int x, int z, const ubyte* voxelData);
|
||||
|
||||
int getVoxelRegionVersion(int x, int z);
|
||||
int getVoxelRegionsVersion();
|
||||
|
||||
ubyte* getChunk(int x, int z);
|
||||
light_t* getLights(int x, int z);
|
||||
chunk_inventories_map fetchInventories(int x, int z);
|
||||
|
||||
bool readWorldInfo(World* world);
|
||||
|
||||
void writeRegion(int x, int y, WorldRegion* entry, fs::path file, int layer);
|
||||
|
||||
/// @brief Write all unsaved data to world files
|
||||
/// @param world target world
|
||||
/// @param content world content
|
||||
void write(const World* world, const Content* content);
|
||||
|
||||
void writePacks(const World* world);
|
||||
void writeIndices(const ContentIndices* indices);
|
||||
|
||||
/// @brief Append pack to the packs list without duplicate check and
|
||||
/// dependencies resolve
|
||||
/// @param world target world
|
||||
/// @param id pack id
|
||||
void addPack(const World* world, const std::string& id);
|
||||
|
||||
/// @brief Remove pack from the list (does not remove indices)
|
||||
/// @param world target world
|
||||
/// @param id pack id
|
||||
void removePack(const World* world, const std::string& id);
|
||||
|
||||
static const inline std::string WORLD_FILE = "world.json";
|
||||
};
|
||||
|
||||
#endif /* FILES_WORLDFILES_H_ */
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef FILES_WORLD_FILES_HPP_
|
||||
#define FILES_WORLD_FILES_HPP_
|
||||
|
||||
#include "WorldRegions.hpp"
|
||||
|
||||
#include "files.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
#include "../settings.hpp"
|
||||
#include "../content/ContentPack.hpp"
|
||||
#include "../voxels/Chunk.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include "glm/gtx/hash.hpp"
|
||||
|
||||
inline constexpr uint WORLD_FORMAT_VERSION = 1;
|
||||
|
||||
class Player;
|
||||
class Content;
|
||||
class ContentIndices;
|
||||
class World;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class WorldFiles {
|
||||
fs::path directory;
|
||||
WorldRegions regions;
|
||||
|
||||
bool generatorTestMode = false;
|
||||
bool doWriteLights = true;
|
||||
|
||||
fs::path getWorldFile() const;
|
||||
fs::path getIndicesFile() const;
|
||||
fs::path getPacksFile() const;
|
||||
|
||||
void writeWorldInfo(const World* world);
|
||||
void writeIndices(const ContentIndices* indices);
|
||||
public:
|
||||
WorldFiles(fs::path directory);
|
||||
WorldFiles(fs::path directory, const DebugSettings& settings);
|
||||
~WorldFiles();
|
||||
|
||||
fs::path getPlayerFile() const;
|
||||
void createDirectories();
|
||||
|
||||
bool readWorldInfo(World* world);
|
||||
|
||||
/// @brief Write all unsaved data to world files
|
||||
/// @param world target world
|
||||
/// @param content world content
|
||||
void write(const World* world, const Content* content);
|
||||
|
||||
void writePacks(const std::vector<ContentPack>& packs);
|
||||
|
||||
void removeIndices(const std::vector<std::string>& packs);
|
||||
|
||||
/// @return world folder
|
||||
fs::path getFolder() const;
|
||||
|
||||
static const inline std::string WORLD_FILE = "world.json";
|
||||
|
||||
WorldRegions& getRegions() {
|
||||
return regions;
|
||||
}
|
||||
|
||||
bool doesWriteLights() const {
|
||||
return doWriteLights;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FILES_WORLD_FILES_HPP_
|
||||
@@ -0,0 +1,479 @@
|
||||
#include "WorldRegions.hpp"
|
||||
|
||||
#include "../coders/byte_utils.hpp"
|
||||
#include "../coders/rle.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
#include "../items/Inventory.hpp"
|
||||
#include "../maths/voxmaths.hpp"
|
||||
#include "../util/data_io.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define REGION_FORMAT_MAGIC ".VOXREG"
|
||||
|
||||
regfile::regfile(fs::path filename) : file(filename) {
|
||||
if (file.length() < REGION_HEADER_SIZE)
|
||||
throw std::runtime_error("incomplete region file header");
|
||||
char header[REGION_HEADER_SIZE];
|
||||
file.read(header, REGION_HEADER_SIZE);
|
||||
|
||||
// avoid of use strcmp_s
|
||||
if (std::string(header, strlen(REGION_FORMAT_MAGIC)) != REGION_FORMAT_MAGIC) {
|
||||
throw std::runtime_error("invalid region file magic number");
|
||||
}
|
||||
version = header[8];
|
||||
if (uint(version) > REGION_FORMAT_VERSION) {
|
||||
throw illegal_region_format(
|
||||
"region format "+std::to_string(version)+" is not supported"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> regfile::read(int index, uint32_t& length) {
|
||||
size_t file_size = file.length();
|
||||
size_t table_offset = file_size - REGION_CHUNKS_COUNT * 4;
|
||||
|
||||
uint32_t offset;
|
||||
file.seekg(table_offset + index * 4);
|
||||
file.read((char*)(&offset), 4);
|
||||
offset = dataio::read_int32_big((const ubyte*)(&offset), 0);
|
||||
if (offset == 0){
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
file.seekg(offset);
|
||||
file.read((char*)(&offset), 4);
|
||||
length = dataio::read_int32_big((const ubyte*)(&offset), 0);
|
||||
auto data = std::make_unique<ubyte[]>(length);
|
||||
file.read((char*)data.get(), length);
|
||||
return data;
|
||||
}
|
||||
|
||||
WorldRegion::WorldRegion() {
|
||||
chunksData = new ubyte*[REGION_CHUNKS_COUNT]{};
|
||||
sizes = new uint32_t[REGION_CHUNKS_COUNT]{};
|
||||
}
|
||||
|
||||
WorldRegion::~WorldRegion() {
|
||||
for (uint i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
delete[] chunksData[i];
|
||||
}
|
||||
delete[] sizes;
|
||||
delete[] chunksData;
|
||||
}
|
||||
|
||||
void WorldRegion::setUnsaved(bool unsaved) {
|
||||
this->unsaved = unsaved;
|
||||
}
|
||||
bool WorldRegion::isUnsaved() const {
|
||||
return unsaved;
|
||||
}
|
||||
|
||||
ubyte** WorldRegion::getChunks() const {
|
||||
return chunksData;
|
||||
}
|
||||
|
||||
uint32_t* WorldRegion::getSizes() const {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
void WorldRegion::put(uint x, uint z, ubyte* data, uint32_t size) {
|
||||
size_t chunk_index = z * REGION_SIZE + x;
|
||||
delete[] chunksData[chunk_index];
|
||||
chunksData[chunk_index] = data;
|
||||
sizes[chunk_index] = size;
|
||||
}
|
||||
|
||||
ubyte* WorldRegion::getChunkData(uint x, uint z) {
|
||||
return chunksData[z * REGION_SIZE + x];
|
||||
}
|
||||
|
||||
uint WorldRegion::getChunkDataSize(uint x, uint z) {
|
||||
return sizes[z * REGION_SIZE + x];
|
||||
}
|
||||
|
||||
WorldRegions::WorldRegions(fs::path directory) : directory(directory) {
|
||||
for (uint i = 0; i < sizeof(layers)/sizeof(RegionsLayer); i++) {
|
||||
layers[i].layer = i;
|
||||
}
|
||||
layers[REGION_LAYER_VOXELS].folder = directory/fs::path("regions");
|
||||
layers[REGION_LAYER_LIGHTS].folder = directory/fs::path("lights");
|
||||
layers[REGION_LAYER_INVENTORIES].folder = directory/fs::path("inventories");
|
||||
}
|
||||
|
||||
WorldRegions::~WorldRegions() {
|
||||
}
|
||||
|
||||
WorldRegion* WorldRegions::getRegion(int x, int z, int layer) {
|
||||
RegionsLayer& regions = layers[layer];
|
||||
std::lock_guard lock(regions.mutex);
|
||||
auto found = regions.regions.find(glm::ivec2(x, z));
|
||||
if (found == regions.regions.end())
|
||||
return nullptr;
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
WorldRegion* WorldRegions::getOrCreateRegion(int x, int z, int layer) {
|
||||
WorldRegion* region = getRegion(x, z, layer);
|
||||
if (region == nullptr) {
|
||||
RegionsLayer& regions = layers[layer];
|
||||
std::lock_guard lock(regions.mutex);
|
||||
region = new WorldRegion();
|
||||
regions.regions[glm::ivec2(x, z)].reset(region);
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> WorldRegions::compress(const ubyte* src, size_t srclen, size_t& len) {
|
||||
auto buffer = bufferPool.get();
|
||||
ubyte* bytes = buffer.get();
|
||||
|
||||
len = extrle::encode(src, srclen, bytes);
|
||||
auto data = std::make_unique<ubyte[]>(len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data[i] = bytes[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> WorldRegions::decompress(const ubyte* src, size_t srclen, size_t dstlen) {
|
||||
auto decompressed = std::make_unique<ubyte[]>(dstlen);
|
||||
extrle::decode(src, srclen, decompressed.get());
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
inline void calc_reg_coords(
|
||||
int x, int z, int& regionX, int& regionZ, int& localX, int& localZ
|
||||
) {
|
||||
regionX = floordiv(x, REGION_SIZE);
|
||||
regionZ = floordiv(z, REGION_SIZE);
|
||||
localX = x - (regionX * REGION_SIZE);
|
||||
localZ = z - (regionZ * REGION_SIZE);
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> WorldRegions::readChunkData(
|
||||
int x,
|
||||
int z,
|
||||
uint32_t& length,
|
||||
regfile* rfile
|
||||
){
|
||||
int regionX, regionZ, localX, localZ;
|
||||
calc_reg_coords(x, z, regionX, regionZ, localX, localZ);
|
||||
int chunkIndex = localZ * REGION_SIZE + localX;
|
||||
return rfile->read(chunkIndex, length);
|
||||
}
|
||||
|
||||
/// @brief Read missing chunks data (null pointers) from region file
|
||||
void WorldRegions::fetchChunks(WorldRegion* region, int x, int z, regfile* file) {
|
||||
ubyte** chunks = region->getChunks();
|
||||
uint32_t* sizes = region->getSizes();
|
||||
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
int chunk_x = (i % REGION_SIZE) + x * REGION_SIZE;
|
||||
int chunk_z = (i / REGION_SIZE) + z * REGION_SIZE;
|
||||
if (chunks[i] == nullptr) {
|
||||
chunks[i] = readChunkData(chunk_x, chunk_z, sizes[i], file).release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ubyte* WorldRegions::getData(
|
||||
int x, int z, int layer,
|
||||
uint32_t& size
|
||||
) {
|
||||
if (generatorTestMode) {
|
||||
return nullptr;
|
||||
}
|
||||
int regionX, regionZ, localX, localZ;
|
||||
calc_reg_coords(x, z, regionX, regionZ, localX, localZ);
|
||||
|
||||
WorldRegion* region = getOrCreateRegion(regionX, regionZ, layer);
|
||||
ubyte* data = region->getChunkData(localX, localZ);
|
||||
if (data == nullptr) {
|
||||
auto regfile = getRegFile(glm::ivec3(regionX, regionZ, layer));
|
||||
if (regfile != nullptr) {
|
||||
data = readChunkData(x, z, size, regfile.get()).release();
|
||||
}
|
||||
if (data != nullptr) {
|
||||
region->put(localX, localZ, data, size);
|
||||
}
|
||||
}
|
||||
if (data != nullptr) {
|
||||
size = region->getChunkDataSize(localX, localZ);
|
||||
return data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<regfile> WorldRegions::useRegFile(glm::ivec3 coord) {
|
||||
auto* file = openRegFiles[coord].get();
|
||||
file->inUse = true;
|
||||
return std::shared_ptr<regfile>(file, [this](regfile* ptr) {
|
||||
ptr->inUse = false;
|
||||
regFilesCv.notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
void WorldRegions::closeRegFile(glm::ivec3 coord) {
|
||||
openRegFiles.erase(coord);
|
||||
regFilesCv.notify_one();
|
||||
}
|
||||
|
||||
// Marks regfile as used and unmarks when shared_ptr dies
|
||||
std::shared_ptr<regfile> WorldRegions::getRegFile(glm::ivec3 coord, bool create) {
|
||||
{
|
||||
std::lock_guard lock(regFilesMutex);
|
||||
const auto found = openRegFiles.find(coord);
|
||||
if (found != openRegFiles.end()) {
|
||||
if (found->second->inUse) {
|
||||
throw std::runtime_error("regfile is currently in use");
|
||||
}
|
||||
return useRegFile(found->first);
|
||||
}
|
||||
}
|
||||
if (create) {
|
||||
return createRegFile(coord);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<regfile> WorldRegions::createRegFile(glm::ivec3 coord) {
|
||||
fs::path file = layers[coord[2]].folder/getRegionFilename(coord[0], coord[1]);
|
||||
if (!fs::exists(file)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (openRegFiles.size() == MAX_OPEN_REGION_FILES) {
|
||||
std::unique_lock lock(regFilesMutex);
|
||||
while (true) {
|
||||
bool closed = false;
|
||||
// FIXME: bad choosing algorithm
|
||||
for (auto& entry : openRegFiles) {
|
||||
if (!entry.second->inUse) {
|
||||
closeRegFile(entry.first);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (closed) {
|
||||
break;
|
||||
}
|
||||
// notified when any regfile gets out of use or closed
|
||||
regFilesCv.wait(lock);
|
||||
}
|
||||
openRegFiles[coord] = std::make_unique<regfile>(file);
|
||||
return useRegFile(coord);
|
||||
} else {
|
||||
std::lock_guard lock(regFilesMutex);
|
||||
openRegFiles[coord] = std::make_unique<regfile>(file);
|
||||
return useRegFile(coord);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path WorldRegions::getRegionFilename(int x, int z) const {
|
||||
return fs::path(std::to_string(x) + "_" + std::to_string(z) + ".bin");
|
||||
}
|
||||
|
||||
void WorldRegions::writeRegion(int x, int z, int layer, WorldRegion* entry){
|
||||
fs::path filename = layers[layer].folder/getRegionFilename(x, z);
|
||||
|
||||
glm::ivec3 regcoord(x, z, layer);
|
||||
if (auto regfile = getRegFile(regcoord, false)) {
|
||||
fetchChunks(entry, x, z, regfile.get());
|
||||
|
||||
std::lock_guard lock(regFilesMutex);
|
||||
closeRegFile(regcoord);
|
||||
}
|
||||
|
||||
char header[REGION_HEADER_SIZE] = REGION_FORMAT_MAGIC;
|
||||
header[8] = REGION_FORMAT_VERSION;
|
||||
header[9] = 0; // flags
|
||||
std::ofstream file(filename, std::ios::out | std::ios::binary);
|
||||
file.write(header, REGION_HEADER_SIZE);
|
||||
|
||||
size_t offset = REGION_HEADER_SIZE;
|
||||
char intbuf[4]{};
|
||||
uint offsets[REGION_CHUNKS_COUNT]{};
|
||||
|
||||
ubyte** region = entry->getChunks();
|
||||
uint32_t* sizes = entry->getSizes();
|
||||
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
ubyte* chunk = region[i];
|
||||
if (chunk == nullptr){
|
||||
offsets[i] = 0;
|
||||
} else {
|
||||
offsets[i] = offset;
|
||||
|
||||
size_t compressedSize = sizes[i];
|
||||
dataio::write_int32_big(compressedSize, (ubyte*)intbuf, 0);
|
||||
offset += 4 + compressedSize;
|
||||
|
||||
file.write(intbuf, 4);
|
||||
file.write((const char*)chunk, compressedSize);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
|
||||
dataio::write_int32_big(offsets[i], (ubyte*)intbuf, 0);
|
||||
file.write(intbuf, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRegions::writeRegions(int layer) {
|
||||
for (auto& it : layers[layer].regions){
|
||||
WorldRegion* region = it.second.get();
|
||||
if (region->getChunks() == nullptr || !region->isUnsaved())
|
||||
continue;
|
||||
glm::ivec2 key = it.first;
|
||||
writeRegion(key[0], key[1], layer, region);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRegions::put(int x, int z, int layer, std::unique_ptr<ubyte[]> data, size_t size, bool rle) {
|
||||
if (rle) {
|
||||
size_t compressedSize;
|
||||
auto compressed = compress(data.get(), size, compressedSize);
|
||||
put(x, z, layer, std::move(compressed), compressedSize, false);
|
||||
return;
|
||||
}
|
||||
int regionX, regionZ, localX, localZ;
|
||||
calc_reg_coords(x, z, regionX, regionZ, localX, localZ);
|
||||
|
||||
WorldRegion* region = getOrCreateRegion(regionX, regionZ, layer);
|
||||
region->setUnsaved(true);
|
||||
region->put(localX, localZ, data.release(), size);
|
||||
}
|
||||
|
||||
static std::unique_ptr<ubyte[]> write_inventories(Chunk* chunk, uint& datasize) {
|
||||
auto& inventories = chunk->inventories;
|
||||
ByteBuilder builder;
|
||||
builder.putInt32(inventories.size());
|
||||
for (auto& entry : inventories) {
|
||||
builder.putInt32(entry.first);
|
||||
auto map = entry.second->serialize();
|
||||
auto bytes = json::to_binary(map.get(), true);
|
||||
builder.putInt32(bytes.size());
|
||||
builder.put(bytes.data(), bytes.size());
|
||||
}
|
||||
auto datavec = builder.data();
|
||||
datasize = builder.size();
|
||||
auto data = std::make_unique<ubyte[]>(datasize);
|
||||
for (uint i = 0; i < datasize; i++) {
|
||||
data[i] = datavec[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// @brief Store chunk (voxels and lights) in region (existing or new)
|
||||
void WorldRegions::put(Chunk* chunk){
|
||||
assert(chunk != nullptr);
|
||||
|
||||
int regionX, regionZ, localX, localZ;
|
||||
calc_reg_coords(chunk->x, chunk->z, regionX, regionZ, localX, localZ);
|
||||
|
||||
put(chunk->x, chunk->z, REGION_LAYER_VOXELS,
|
||||
chunk->encode(), CHUNK_DATA_LEN, true);
|
||||
|
||||
// Writing lights cache
|
||||
if (doWriteLights && chunk->isLighted()) {
|
||||
put(chunk->x, chunk->z, REGION_LAYER_LIGHTS,
|
||||
chunk->lightmap.encode(), LIGHTMAP_DATA_LEN, true);
|
||||
}
|
||||
// Writing block inventories
|
||||
if (!chunk->inventories.empty()){
|
||||
uint datasize;
|
||||
auto data = write_inventories(chunk, datasize);
|
||||
put(chunk->x, chunk->z, REGION_LAYER_INVENTORIES,
|
||||
std::move(data), datasize, false);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> WorldRegions::getChunk(int x, int z){
|
||||
uint32_t size;
|
||||
auto* data = getData(x, z, REGION_LAYER_VOXELS, size);
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return decompress(data, size, CHUNK_DATA_LEN);
|
||||
}
|
||||
|
||||
/// @brief Get cached lights for chunk at x,z
|
||||
/// @return lights data or nullptr
|
||||
std::unique_ptr<light_t[]> WorldRegions::getLights(int x, int z) {
|
||||
uint32_t size;
|
||||
auto* bytes = getData(x, z, REGION_LAYER_LIGHTS, size);
|
||||
if (bytes == nullptr)
|
||||
return nullptr;
|
||||
auto data = decompress(bytes, size, LIGHTMAP_DATA_LEN);
|
||||
return Lightmap::decode(data.get());
|
||||
}
|
||||
|
||||
chunk_inventories_map WorldRegions::fetchInventories(int x, int z) {
|
||||
chunk_inventories_map inventories;
|
||||
uint32_t bytesSize;
|
||||
const ubyte* data = getData(x, z, REGION_LAYER_INVENTORIES, bytesSize);
|
||||
if (data == nullptr)
|
||||
return inventories;
|
||||
ByteReader reader(data, bytesSize);
|
||||
int count = reader.getInt32();
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint index = reader.getInt32();
|
||||
uint size = reader.getInt32();
|
||||
auto map = json::from_binary(reader.pointer(), size);
|
||||
reader.skip(size);
|
||||
auto inv = std::make_shared<Inventory>(0, 0);
|
||||
inv->deserialize(map.get());
|
||||
inventories[index] = inv;
|
||||
}
|
||||
return inventories;
|
||||
}
|
||||
|
||||
void WorldRegions::processRegionVoxels(int x, int z, regionproc func) {
|
||||
if (getRegion(x, z, REGION_LAYER_VOXELS)) {
|
||||
throw std::runtime_error("not implemented for in-memory regions");
|
||||
}
|
||||
auto regfile = getRegFile(glm::ivec3(x, z, REGION_LAYER_VOXELS));
|
||||
if (regfile == nullptr) {
|
||||
throw std::runtime_error("could not open region file");
|
||||
}
|
||||
for (uint cz = 0; cz < REGION_SIZE; cz++) {
|
||||
for (uint cx = 0; cx < REGION_SIZE; cx++) {
|
||||
int gx = cx + x * REGION_SIZE;
|
||||
int gz = cz + z * REGION_SIZE;
|
||||
uint32_t length;
|
||||
auto data = readChunkData(gx, gz, length, regfile.get());
|
||||
if (data == nullptr)
|
||||
continue;
|
||||
data = decompress(data.get(), length, CHUNK_DATA_LEN);
|
||||
if (func(data.get())) {
|
||||
put(gx, gz, REGION_LAYER_VOXELS, std::move(data), CHUNK_DATA_LEN, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::path WorldRegions::getRegionsFolder(int layer) const {
|
||||
return layers[layer].folder;
|
||||
}
|
||||
|
||||
|
||||
void WorldRegions::write() {
|
||||
for (auto& layer : layers) {
|
||||
fs::create_directories(layer.folder);
|
||||
writeRegions(layer.layer);
|
||||
}
|
||||
}
|
||||
|
||||
bool WorldRegions::parseRegionFilename(const std::string& name, int& x, int& z) {
|
||||
size_t sep = name.find('_');
|
||||
if (sep == std::string::npos || sep == 0 || sep == name.length()-1)
|
||||
return false;
|
||||
try {
|
||||
x = std::stoi(name.substr(0, sep));
|
||||
z = std::stoi(name.substr(sep+1));
|
||||
} catch (std::invalid_argument& err) {
|
||||
return false;
|
||||
} catch (std::out_of_range& err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef FILES_WORLD_REGIONS_HPP_
|
||||
#define FILES_WORLD_REGIONS_HPP_
|
||||
|
||||
#include "files.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
#include "../util/BufferPool.hpp"
|
||||
#include "../voxels/Chunk.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include "glm/gtx/hash.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
inline constexpr uint REGION_HEADER_SIZE = 10;
|
||||
|
||||
inline constexpr uint REGION_LAYER_VOXELS = 0;
|
||||
inline constexpr uint REGION_LAYER_LIGHTS = 1;
|
||||
inline constexpr uint REGION_LAYER_INVENTORIES = 2;
|
||||
|
||||
inline constexpr uint REGION_SIZE_BIT = 5;
|
||||
inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT));
|
||||
inline constexpr uint REGION_CHUNKS_COUNT = ((REGION_SIZE) * (REGION_SIZE));
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 2;
|
||||
inline constexpr uint MAX_OPEN_REGION_FILES = 16;
|
||||
|
||||
class illegal_region_format : public std::runtime_error {
|
||||
public:
|
||||
illegal_region_format(const std::string& message)
|
||||
: std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
class WorldRegion {
|
||||
ubyte** chunksData;
|
||||
uint32_t* sizes;
|
||||
bool unsaved = false;
|
||||
public:
|
||||
WorldRegion();
|
||||
~WorldRegion();
|
||||
|
||||
void put(uint x, uint z, ubyte* data, uint32_t size);
|
||||
ubyte* getChunkData(uint x, uint z);
|
||||
uint getChunkDataSize(uint x, uint z);
|
||||
|
||||
void setUnsaved(bool unsaved);
|
||||
bool isUnsaved() const;
|
||||
|
||||
ubyte** getChunks() const;
|
||||
uint32_t* getSizes() const;
|
||||
};
|
||||
|
||||
struct regfile {
|
||||
files::rafile file;
|
||||
int version;
|
||||
bool inUse = false;
|
||||
|
||||
regfile(fs::path filename);
|
||||
regfile(const regfile&) = delete;
|
||||
|
||||
std::unique_ptr<ubyte[]> read(int index, uint32_t& length);
|
||||
};
|
||||
|
||||
using regionsmap = std::unordered_map<glm::ivec2, std::unique_ptr<WorldRegion>>;
|
||||
using regionproc = std::function<bool(ubyte*)>;
|
||||
|
||||
struct RegionsLayer {
|
||||
int layer;
|
||||
fs::path folder;
|
||||
regionsmap regions;
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
class WorldRegions {
|
||||
fs::path directory;
|
||||
std::unordered_map<glm::ivec3, std::unique_ptr<regfile>> openRegFiles;
|
||||
std::mutex regFilesMutex;
|
||||
std::condition_variable regFilesCv;
|
||||
RegionsLayer layers[3] {};
|
||||
util::BufferPool<ubyte> bufferPool {
|
||||
std::max(CHUNK_DATA_LEN, LIGHTMAP_DATA_LEN) * 2
|
||||
};
|
||||
|
||||
WorldRegion* getRegion(int x, int z, int layer);
|
||||
WorldRegion* getOrCreateRegion(int x, int z, int layer);
|
||||
|
||||
/// @brief Compress buffer with extrle
|
||||
/// @param src source buffer
|
||||
/// @param srclen length of the source buffer
|
||||
/// @param len (out argument) length of result buffer
|
||||
/// @return compressed bytes array
|
||||
std::unique_ptr<ubyte[]> compress(const ubyte* src, size_t srclen, size_t& len);
|
||||
|
||||
/// @brief Decompress buffer with extrle
|
||||
/// @param src compressed buffer
|
||||
/// @param srclen length of compressed buffer
|
||||
/// @param dstlen max expected length of source buffer
|
||||
/// @return decompressed bytes array
|
||||
std::unique_ptr<ubyte[]> decompress(const ubyte* src, size_t srclen, size_t dstlen);
|
||||
|
||||
std::unique_ptr<ubyte[]> readChunkData(int x, int y, uint32_t& length, regfile* file);
|
||||
|
||||
void fetchChunks(WorldRegion* region, int x, int y, regfile* file);
|
||||
|
||||
ubyte* getData(int x, int z, int layer, uint32_t& size);
|
||||
|
||||
std::shared_ptr<regfile> getRegFile(glm::ivec3 coord, bool create=true);
|
||||
void closeRegFile(glm::ivec3 coord);
|
||||
std::shared_ptr<regfile> useRegFile(glm::ivec3 coord);
|
||||
std::shared_ptr<regfile> createRegFile(glm::ivec3 coord);
|
||||
|
||||
fs::path getRegionFilename(int x, int y) const;
|
||||
|
||||
void writeRegions(int layer);
|
||||
|
||||
/// @brief Write or rewrite region file
|
||||
/// @param x region X
|
||||
/// @param z region Z
|
||||
/// @param layer regions layer
|
||||
void writeRegion(int x, int y, int layer, WorldRegion* entry);
|
||||
public:
|
||||
bool generatorTestMode = false;
|
||||
bool doWriteLights = true;
|
||||
|
||||
WorldRegions(fs::path directory);
|
||||
WorldRegions(const WorldRegions&) = delete;
|
||||
~WorldRegions();
|
||||
|
||||
/// @brief Put all chunk data to regions
|
||||
void put(Chunk* chunk);
|
||||
|
||||
/// @brief Store data in specified region
|
||||
/// @param x chunk.x
|
||||
/// @param z chunk.z
|
||||
/// @param layer regions layer
|
||||
/// @param data target data
|
||||
/// @param size data size
|
||||
/// @param rle compress with ext-RLE
|
||||
void put(int x, int z, int layer, std::unique_ptr<ubyte[]> data, size_t size, bool rle);
|
||||
|
||||
std::unique_ptr<ubyte[]> getChunk(int x, int z);
|
||||
std::unique_ptr<light_t[]> getLights(int x, int z);
|
||||
chunk_inventories_map fetchInventories(int x, int z);
|
||||
|
||||
void processRegionVoxels(int x, int z, regionproc func);
|
||||
|
||||
fs::path getRegionsFolder(int layer) const;
|
||||
|
||||
void write();
|
||||
|
||||
/// @brief Extract X and Z from 'X_Z.bin' region file name.
|
||||
/// @param name source region file name
|
||||
/// @param x parsed X destination
|
||||
/// @param z parsed Z destination
|
||||
/// @return false if std::invalid_argument or std::out_of_range occurred
|
||||
static bool parseRegionFilename(const std::string& name, int& x, int& y);
|
||||
};
|
||||
|
||||
#endif // FILES_WORLD_REGIONS_HPP_
|
||||
@@ -1,13 +1,17 @@
|
||||
#include "engine_paths.h"
|
||||
#include "engine_paths.hpp"
|
||||
|
||||
#include <stack>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../typedefs.h"
|
||||
#include "WorldFiles.h"
|
||||
#include "../util/stringutil.hpp"
|
||||
#include "../typedefs.hpp"
|
||||
#include "WorldFiles.hpp"
|
||||
|
||||
const fs::path SCREENSHOTS_FOLDER {"screenshots"};
|
||||
const fs::path CONTROLS_FILE {"controls.json"};
|
||||
const fs::path SETTINGS_FILE {"settings.toml"};
|
||||
|
||||
fs::path EnginePaths::getUserfiles() const {
|
||||
return userfiles;
|
||||
@@ -48,6 +52,18 @@ fs::path EnginePaths::getWorldFolder() {
|
||||
return worldFolder;
|
||||
}
|
||||
|
||||
fs::path EnginePaths::getWorldFolder(const std::string& name) {
|
||||
return getWorldsFolder()/fs::path(name);
|
||||
}
|
||||
|
||||
fs::path EnginePaths::getControlsFile() {
|
||||
return userfiles/fs::path(CONTROLS_FILE);
|
||||
}
|
||||
|
||||
fs::path EnginePaths::getSettingsFile() {
|
||||
return userfiles/fs::path(SETTINGS_FILE);
|
||||
}
|
||||
|
||||
std::vector<fs::path> EnginePaths::scanForWorlds() {
|
||||
std::vector<fs::path> folders;
|
||||
|
||||
@@ -148,14 +164,14 @@ fs::path EnginePaths::resolve(std::string path) {
|
||||
throw files_access_error("unknown entry point '"+prefix+"'");
|
||||
}
|
||||
|
||||
ResPaths::ResPaths(fs::path mainRoot, std::vector<fs::path> roots)
|
||||
ResPaths::ResPaths(fs::path mainRoot, std::vector<PathsRoot> roots)
|
||||
: mainRoot(mainRoot), roots(roots) {
|
||||
}
|
||||
|
||||
fs::path ResPaths::find(const std::string& filename) const {
|
||||
for (int i = roots.size()-1; i >= 0; i--) {
|
||||
auto& root = roots[i];
|
||||
fs::path file = root / fs::u8path(filename);
|
||||
fs::path file = root.path / fs::u8path(filename);
|
||||
if (fs::exists(file)) {
|
||||
return file;
|
||||
}
|
||||
@@ -163,11 +179,49 @@ fs::path ResPaths::find(const std::string& filename) const {
|
||||
return mainRoot / fs::u8path(filename);
|
||||
}
|
||||
|
||||
std::string ResPaths::findRaw(const std::string& filename) const {
|
||||
for (int i = roots.size()-1; i >= 0; i--) {
|
||||
auto& root = roots[i];
|
||||
if (fs::exists(root.path / fs::path(filename))) {
|
||||
return root.name + ":" + filename;
|
||||
}
|
||||
}
|
||||
auto resDir = mainRoot;
|
||||
if (fs::exists(resDir / fs::path(filename))) {
|
||||
return "core:"+filename;
|
||||
}
|
||||
throw std::runtime_error("could not to find file "+util::quote(filename));
|
||||
}
|
||||
|
||||
std::vector<std::string> ResPaths::listdirRaw(const std::string& folderName) const {
|
||||
std::vector<std::string> entries;
|
||||
for (int i = roots.size()-1; i >= 0; i--) {
|
||||
auto& root = roots[i];
|
||||
fs::path folder = root.path / fs::u8path(folderName);
|
||||
if (!fs::is_directory(folder))
|
||||
continue;
|
||||
for (const auto& entry : fs::directory_iterator(folder)) {
|
||||
auto name = entry.path().filename().u8string();
|
||||
entries.push_back(root.name+":"+folderName+"/"+name);
|
||||
}
|
||||
}
|
||||
{
|
||||
fs::path folder = mainRoot / fs::u8path(folderName);
|
||||
if (!fs::is_directory(folder))
|
||||
return entries;
|
||||
for (const auto& entry : fs::directory_iterator(folder)) {
|
||||
auto name = entry.path().filename().u8string();
|
||||
entries.push_back("core:"+folderName+"/"+name);
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<fs::path> ResPaths::listdir(const std::string& folderName) const {
|
||||
std::vector<fs::path> entries;
|
||||
for (int i = roots.size()-1; i >= 0; i--) {
|
||||
auto& root = roots[i];
|
||||
fs::path folder = root / fs::u8path(folderName);
|
||||
fs::path folder = root.path / fs::u8path(folderName);
|
||||
if (!fs::is_directory(folder))
|
||||
continue;
|
||||
for (const auto& entry : fs::directory_iterator(folder)) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifndef FILES_ENGINE_PATHS_H_
|
||||
#define FILES_ENGINE_PATHS_H_
|
||||
#ifndef FILES_ENGINE_PATHS_HPP_
|
||||
#define FILES_ENGINE_PATHS_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../content/ContentPack.h"
|
||||
#include "../content/ContentPack.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -27,6 +27,9 @@ public:
|
||||
fs::path getScreenshotFile(std::string ext);
|
||||
fs::path getWorldsFolder();
|
||||
fs::path getWorldFolder();
|
||||
fs::path getWorldFolder(const std::string& name);
|
||||
fs::path getControlsFile();
|
||||
fs::path getSettingsFile();
|
||||
bool isWorldNameUsed(std::string name);
|
||||
|
||||
void setUserfiles(fs::path folder);
|
||||
@@ -39,17 +42,26 @@ public:
|
||||
fs::path resolve(std::string path);
|
||||
};
|
||||
|
||||
struct PathsRoot {
|
||||
std::string name;
|
||||
fs::path path;
|
||||
};
|
||||
|
||||
class ResPaths {
|
||||
fs::path mainRoot;
|
||||
std::vector<fs::path> roots;
|
||||
std::vector<PathsRoot> roots;
|
||||
public:
|
||||
ResPaths(fs::path mainRoot,
|
||||
std::vector<fs::path> roots);
|
||||
ResPaths(
|
||||
fs::path mainRoot,
|
||||
std::vector<PathsRoot> roots
|
||||
);
|
||||
|
||||
fs::path find(const std::string& filename) const;
|
||||
std::string findRaw(const std::string& filename) const;
|
||||
std::vector<fs::path> listdir(const std::string& folder) const;
|
||||
std::vector<std::string> listdirRaw(const std::string& folder) const;
|
||||
|
||||
const fs::path& getMainRoot() const;
|
||||
};
|
||||
|
||||
#endif // FILES_ENGINE_PATHS_H_
|
||||
#endif // FILES_ENGINE_PATHS_HPP_
|
||||
+70
-71
@@ -1,14 +1,15 @@
|
||||
#include "files.h"
|
||||
#include "files.hpp"
|
||||
|
||||
#include "../coders/json.hpp"
|
||||
#include "../coders/gzip.hpp"
|
||||
#include "../util/stringutil.hpp"
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
#include "../coders/json.h"
|
||||
#include "../coders/gzip.h"
|
||||
#include "../util/stringutil.h"
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -34,64 +35,64 @@ void files::rafile::read(char* buffer, std::streamsize size) {
|
||||
}
|
||||
|
||||
bool files::write_bytes(fs::path filename, const ubyte* data, size_t size) {
|
||||
std::ofstream output(filename, std::ios::binary);
|
||||
if (!output.is_open())
|
||||
return false;
|
||||
output.write((const char*)data, size);
|
||||
output.close();
|
||||
return true;
|
||||
std::ofstream output(filename, std::ios::binary);
|
||||
if (!output.is_open())
|
||||
return false;
|
||||
output.write((const char*)data, size);
|
||||
output.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint files::append_bytes(fs::path filename, const ubyte* data, size_t size) {
|
||||
std::ofstream output(filename, std::ios::binary | std::ios::app);
|
||||
if (!output.is_open())
|
||||
return 0;
|
||||
uint position = output.tellp();
|
||||
output.write((const char*)data, size);
|
||||
output.close();
|
||||
return position;
|
||||
std::ofstream output(filename, std::ios::binary | std::ios::app);
|
||||
if (!output.is_open())
|
||||
return 0;
|
||||
uint position = output.tellp();
|
||||
output.write((const char*)data, size);
|
||||
output.close();
|
||||
return position;
|
||||
}
|
||||
|
||||
bool files::read(fs::path filename, char* data, size_t size) {
|
||||
std::ifstream output(filename, std::ios::binary);
|
||||
if (!output.is_open())
|
||||
return false;
|
||||
output.read(data, size);
|
||||
output.close();
|
||||
return true;
|
||||
std::ifstream output(filename, std::ios::binary);
|
||||
if (!output.is_open())
|
||||
return false;
|
||||
output.read(data, size);
|
||||
output.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
ubyte* files::read_bytes(fs::path filename, size_t& length) {
|
||||
std::ifstream input(filename, std::ios::binary);
|
||||
if (!input.is_open())
|
||||
return nullptr;
|
||||
input.seekg(0, std::ios_base::end);
|
||||
length = input.tellg();
|
||||
input.seekg(0, std::ios_base::beg);
|
||||
std::unique_ptr<ubyte[]> files::read_bytes(fs::path filename, size_t& length) {
|
||||
std::ifstream input(filename, std::ios::binary);
|
||||
if (!input.is_open())
|
||||
return nullptr;
|
||||
input.seekg(0, std::ios_base::end);
|
||||
length = input.tellg();
|
||||
input.seekg(0, std::ios_base::beg);
|
||||
|
||||
std::unique_ptr<char> data(new char[length]);
|
||||
input.read(data.get(), length);
|
||||
input.close();
|
||||
return (ubyte*)data.release();
|
||||
auto data = std::make_unique<ubyte[]>(length);
|
||||
input.read((char*)data.get(), length);
|
||||
input.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string files::read_string(fs::path filename) {
|
||||
size_t size;
|
||||
std::unique_ptr<ubyte[]> bytes (read_bytes(filename, size));
|
||||
if (bytes == nullptr) {
|
||||
throw std::runtime_error("could not to load file '"+
|
||||
filename.string()+"'");
|
||||
}
|
||||
return std::string((const char*)bytes.get(), size);
|
||||
size_t size;
|
||||
std::unique_ptr<ubyte[]> bytes (read_bytes(filename, size));
|
||||
if (bytes == nullptr) {
|
||||
throw std::runtime_error("could not to load file '"+
|
||||
filename.string()+"'");
|
||||
}
|
||||
return std::string((const char*)bytes.get(), size);
|
||||
}
|
||||
|
||||
bool files::write_string(fs::path filename, const std::string content) {
|
||||
std::ofstream file(filename);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
file << content;
|
||||
return true;
|
||||
std::ofstream file(filename);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
file << content;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool files::write_json(fs::path filename, const dynamic::Map* obj, bool nice) {
|
||||
@@ -103,39 +104,37 @@ bool files::write_binary_json(fs::path filename, const dynamic::Map* obj, bool c
|
||||
return files::write_bytes(filename, bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
std::unique_ptr<dynamic::Map> files::read_json(fs::path filename) {
|
||||
std::string text = files::read_string(filename);
|
||||
try {
|
||||
auto obj = json::parse(filename.string(), text);
|
||||
std::shared_ptr<dynamic::Map> files::read_json(fs::path filename) {
|
||||
std::string text = files::read_string(filename);
|
||||
try {
|
||||
auto obj = json::parse(filename.string(), text);
|
||||
return obj;
|
||||
} catch (const parsing_error& error) {
|
||||
} catch (const parsing_error& error) {
|
||||
std::cerr << error.errorLog() << std::endl;
|
||||
throw std::runtime_error("could not to parse "+filename.string());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<dynamic::Map> files::read_binary_json(fs::path file) {
|
||||
std::shared_ptr<dynamic::Map> files::read_binary_json(fs::path file) {
|
||||
size_t size;
|
||||
std::unique_ptr<ubyte[]> bytes (files::read_bytes(file, size));
|
||||
return std::unique_ptr<dynamic::Map>(
|
||||
json::from_binary(bytes.get(), size)
|
||||
);
|
||||
return json::from_binary(bytes.get(), size);
|
||||
}
|
||||
|
||||
std::vector<std::string> files::read_list(fs::path filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file) {
|
||||
throw std::runtime_error("could not to open file "+filename.u8string());
|
||||
}
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::ifstream file(filename);
|
||||
if (!file) {
|
||||
throw std::runtime_error("could not to open file "+filename.u8string());
|
||||
}
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
util::trim(line);
|
||||
if (line.length() == 0)
|
||||
continue;
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
lines.push_back(line);
|
||||
}
|
||||
return lines;
|
||||
if (line.length() == 0)
|
||||
continue;
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
lines.push_back(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifndef FILES_FILES_H_
|
||||
#define FILES_FILES_H_
|
||||
#ifndef FILES_FILES_HPP_
|
||||
#define FILES_FILES_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include "../typedefs.h"
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -31,20 +31,20 @@ namespace files {
|
||||
/// @param file target file
|
||||
/// @param data data bytes array
|
||||
/// @param size size of data bytes array
|
||||
extern bool write_bytes(fs::path file, const ubyte* data, size_t size);
|
||||
bool write_bytes(fs::path file, const ubyte* data, size_t size);
|
||||
|
||||
/// @brief Append bytes array to the file without any extra data
|
||||
/// @param file target file
|
||||
/// @param data data bytes array
|
||||
/// @param size size of data bytes array
|
||||
extern uint append_bytes(fs::path file, const ubyte* data, size_t size);
|
||||
uint append_bytes(fs::path file, const ubyte* data, size_t size);
|
||||
|
||||
/// @brief Write string to the file
|
||||
extern bool write_string(fs::path filename, const std::string content);
|
||||
bool write_string(fs::path filename, const std::string content);
|
||||
|
||||
/// @brief Write dynamic data to the JSON file
|
||||
/// @param nice if true, human readable format will be used, otherwise minimal
|
||||
extern bool write_json(
|
||||
bool write_json(
|
||||
fs::path filename,
|
||||
const dynamic::Map* obj,
|
||||
bool nice=true);
|
||||
@@ -52,21 +52,21 @@ namespace files {
|
||||
/// @brief Write dynamic data to the binary JSON file
|
||||
/// (see src/coders/binary_json_spec.md)
|
||||
/// @param compressed use gzip compression
|
||||
extern bool write_binary_json(
|
||||
bool write_binary_json(
|
||||
fs::path filename,
|
||||
const dynamic::Map* obj,
|
||||
bool compressed=false
|
||||
);
|
||||
|
||||
extern bool read(fs::path, char* data, size_t size);
|
||||
extern ubyte* read_bytes(fs::path, size_t& length);
|
||||
extern std::string read_string(fs::path filename);
|
||||
bool read(fs::path, char* data, size_t size);
|
||||
std::unique_ptr<ubyte[]> read_bytes(fs::path, size_t& length);
|
||||
std::string read_string(fs::path filename);
|
||||
|
||||
/// @brief Read JSON or BJSON file
|
||||
/// @param file *.json or *.bjson file
|
||||
extern std::unique_ptr<dynamic::Map> read_json(fs::path file);
|
||||
extern std::unique_ptr<dynamic::Map> read_binary_json(fs::path file);
|
||||
extern std::vector<std::string> read_list(fs::path file);
|
||||
std::shared_ptr<dynamic::Map> read_json(fs::path file);
|
||||
std::shared_ptr<dynamic::Map> read_binary_json(fs::path file);
|
||||
std::vector<std::string> read_list(fs::path file);
|
||||
}
|
||||
|
||||
#endif /* FILES_FILES_H_ */
|
||||
#endif /* FILES_FILES_HPP_ */
|
||||
@@ -1,90 +0,0 @@
|
||||
#include "rle.h"
|
||||
|
||||
size_t rle::decode(const ubyte* src, size_t srclen, ubyte* dst) {
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < srclen;) {
|
||||
ubyte len = src[i++];
|
||||
ubyte c = src[i++];
|
||||
for (size_t j = 0; j <= len; j++) {
|
||||
dst[offset++] = c;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t rle::encode(const ubyte* src, size_t srclen, ubyte* dst) {
|
||||
if (srclen == 0) {
|
||||
return 0;
|
||||
}
|
||||
size_t offset = 0;
|
||||
ubyte counter = 0;
|
||||
ubyte c = src[0];
|
||||
for (size_t i = 1; i < srclen; i++) {
|
||||
ubyte cnext = src[i];
|
||||
if (cnext != c || counter == 255) {
|
||||
dst[offset++] = counter;
|
||||
dst[offset++] = c;
|
||||
c = cnext;
|
||||
counter = 0;
|
||||
}
|
||||
else {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
dst[offset++] = counter;
|
||||
dst[offset++] = c;
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
size_t extrle::decode(const ubyte* src, size_t srclen, ubyte* dst) {
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < srclen;) {
|
||||
uint len = src[i++];
|
||||
if (len & 0x80) {
|
||||
len &= 0x7F;
|
||||
len |= ((uint)src[i++]) << 7;
|
||||
}
|
||||
ubyte c = src[i++];
|
||||
for (size_t j = 0; j <= len; j++) {
|
||||
dst[offset++] = c;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t extrle::encode(const ubyte* src, size_t srclen, ubyte* dst) {
|
||||
if (srclen == 0) {
|
||||
return 0;
|
||||
}
|
||||
size_t offset = 0;
|
||||
uint counter = 0;
|
||||
ubyte c = src[0];
|
||||
for (size_t i = 1; i < srclen; i++) {
|
||||
ubyte cnext = src[i];
|
||||
if (cnext != c || counter == max_sequence) {
|
||||
if (counter >= 0x80) {
|
||||
dst[offset++] = 0x80 | (counter & 0x7F);
|
||||
dst[offset++] = counter >> 7;
|
||||
}
|
||||
else {
|
||||
dst[offset++] = counter;
|
||||
}
|
||||
dst[offset++] = c;
|
||||
c = cnext;
|
||||
counter = 0;
|
||||
}
|
||||
else {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
if (counter >= 0x80) {
|
||||
dst[offset++] = 0x80 | (counter & 0x7F);
|
||||
dst[offset++] = counter >> 7;
|
||||
}
|
||||
else {
|
||||
dst[offset++] = counter;
|
||||
}
|
||||
dst[offset++] = c;
|
||||
return offset;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef FILES_RLE_H_
|
||||
#define FILES_RLE_H_
|
||||
|
||||
#include "../typedefs.h"
|
||||
|
||||
namespace rle {
|
||||
size_t encode(const ubyte* src, size_t length, ubyte* dst);
|
||||
size_t decode(const ubyte* src, size_t length, ubyte* dst);
|
||||
}
|
||||
|
||||
namespace extrle {
|
||||
constexpr uint max_sequence = 0x7FFF;
|
||||
size_t encode(const ubyte* src, size_t length, ubyte* dst);
|
||||
size_t decode(const ubyte* src, size_t length, ubyte* dst);
|
||||
}
|
||||
|
||||
#endif // FILES_RLE_H_
|
||||
+150
-85
@@ -1,99 +1,164 @@
|
||||
#include "settings_io.h"
|
||||
#include "settings_io.hpp"
|
||||
|
||||
#include "../window/Events.hpp"
|
||||
#include "../window/input.hpp"
|
||||
#include "../coders/toml.hpp"
|
||||
#include "../coders/json.hpp"
|
||||
#include "../debug/Logger.hpp"
|
||||
#include "../settings.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
#include "../window/Events.h"
|
||||
#include "../window/input.h"
|
||||
static debug::Logger logger("settings_io");
|
||||
|
||||
#include "../coders/toml.h"
|
||||
#include "../coders/json.h"
|
||||
struct SectionsBuilder {
|
||||
std::unordered_map<std::string, Setting*>& map;
|
||||
std::vector<Section>& sections;
|
||||
|
||||
#include "../data/dynamic.h"
|
||||
|
||||
toml::Wrapper* create_wrapper(EngineSettings& settings) {
|
||||
std::unique_ptr<toml::Wrapper> wrapper (new toml::Wrapper());
|
||||
|
||||
toml::Section& audio = wrapper->add("audio");
|
||||
audio.add("enabled", &settings.audio.enabled);
|
||||
audio.add("volume-master", &settings.audio.volumeMaster);
|
||||
audio.add("volume-regular", &settings.audio.volumeRegular);
|
||||
audio.add("volume-ui", &settings.audio.volumeUI);
|
||||
audio.add("volume-ambient", &settings.audio.volumeAmbient);
|
||||
audio.add("volume-music", &settings.audio.volumeMusic);
|
||||
|
||||
toml::Section& display = wrapper->add("display");
|
||||
display.add("fullscreen", &settings.display.fullscreen);
|
||||
display.add("width", &settings.display.width);
|
||||
display.add("height", &settings.display.height);
|
||||
display.add("samples", &settings.display.samples);
|
||||
display.add("swap-interval", &settings.display.swapInterval);
|
||||
|
||||
toml::Section& chunks = wrapper->add("chunks");
|
||||
chunks.add("load-distance", &settings.chunks.loadDistance);
|
||||
chunks.add("load-speed", &settings.chunks.loadSpeed);
|
||||
chunks.add("padding", &settings.chunks.padding);
|
||||
|
||||
toml::Section& camera = wrapper->add("camera");
|
||||
camera.add("fov-effects", &settings.camera.fovEvents);
|
||||
camera.add("fov", &settings.camera.fov);
|
||||
camera.add("shaking", &settings.camera.shaking);
|
||||
camera.add("sensitivity", &settings.camera.sensitivity);
|
||||
|
||||
toml::Section& graphics = wrapper->add("graphics");
|
||||
graphics.add("gamma", &settings.graphics.gamma);
|
||||
graphics.add("fog-curve", &settings.graphics.fogCurve);
|
||||
graphics.add("backlight", &settings.graphics.backlight);
|
||||
graphics.add("frustum-culling", &settings.graphics.frustumCulling);
|
||||
graphics.add("skybox-resolution", &settings.graphics.skyboxResolution);
|
||||
|
||||
toml::Section& debug = wrapper->add("debug");
|
||||
debug.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||
debug.add("show-chunk-borders", &settings.debug.showChunkBorders);
|
||||
debug.add("do-write-lights", &settings.debug.doWriteLights);
|
||||
|
||||
toml::Section& ui = wrapper->add("ui");
|
||||
ui.add("language", &settings.ui.language);
|
||||
return wrapper.release();
|
||||
}
|
||||
|
||||
std::string write_controls() {
|
||||
dynamic::Map obj;
|
||||
for (auto& entry : Events::bindings) {
|
||||
const auto& binding = entry.second;
|
||||
|
||||
auto& jentry = obj.putMap(entry.first);
|
||||
switch (binding.type) {
|
||||
case inputtype::keyboard: jentry.put("type", "keyboard"); break;
|
||||
case inputtype::mouse: jentry.put("type", "mouse"); break;
|
||||
default: throw std::runtime_error("unsupported control type");
|
||||
}
|
||||
jentry.put("code", binding.code);
|
||||
SectionsBuilder(
|
||||
std::unordered_map<std::string, Setting*>& map,
|
||||
std::vector<Section>& sections
|
||||
) : map(map), sections(sections) {
|
||||
}
|
||||
return json::stringify(&obj, true, " ");
|
||||
|
||||
void section(std::string name) {
|
||||
sections.push_back(Section {name, {}});
|
||||
}
|
||||
|
||||
void add(std::string name, Setting* setting, bool writeable=true) {
|
||||
Section& section = sections.at(sections.size()-1);
|
||||
map[section.name+"."+name] = setting;
|
||||
section.keys.push_back(name);
|
||||
}
|
||||
};
|
||||
|
||||
SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
||||
SectionsBuilder builder(map, sections);
|
||||
|
||||
builder.section("audio");
|
||||
builder.add("enabled", &settings.audio.enabled, false);
|
||||
builder.add("volume-master", &settings.audio.volumeMaster);
|
||||
builder.add("volume-regular", &settings.audio.volumeRegular);
|
||||
builder.add("volume-ui", &settings.audio.volumeUI);
|
||||
builder.add("volume-ambient", &settings.audio.volumeAmbient);
|
||||
builder.add("volume-music", &settings.audio.volumeMusic);
|
||||
|
||||
builder.section("display");
|
||||
builder.add("width", &settings.display.width);
|
||||
builder.add("height", &settings.display.height);
|
||||
builder.add("samples", &settings.display.samples);
|
||||
builder.add("vsync", &settings.display.vsync);
|
||||
builder.add("fullscreen", &settings.display.fullscreen);
|
||||
|
||||
builder.section("camera");
|
||||
builder.add("sensitivity", &settings.camera.sensitivity);
|
||||
builder.add("fov", &settings.camera.fov);
|
||||
builder.add("fov-effects", &settings.camera.fovEffects);
|
||||
builder.add("shaking", &settings.camera.shaking);
|
||||
|
||||
builder.section("chunks");
|
||||
builder.add("load-distance", &settings.chunks.loadDistance);
|
||||
builder.add("load-speed", &settings.chunks.loadSpeed);
|
||||
builder.add("padding", &settings.chunks.padding);
|
||||
|
||||
builder.section("graphics");
|
||||
builder.add("fog-curve", &settings.graphics.fogCurve);
|
||||
builder.add("backlight", &settings.graphics.backlight);
|
||||
builder.add("gamma", &settings.graphics.gamma);
|
||||
builder.add("frustum-culling", &settings.graphics.frustumCulling);
|
||||
builder.add("skybox-resolution", &settings.graphics.skyboxResolution);
|
||||
|
||||
builder.section("ui");
|
||||
builder.add("language", &settings.ui.language);
|
||||
builder.add("world-preview-size", &settings.ui.worldPreviewSize);
|
||||
|
||||
builder.section("debug");
|
||||
builder.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||
builder.add("do-write-lights", &settings.debug.doWriteLights);
|
||||
}
|
||||
|
||||
void load_controls(std::string filename, std::string source) {
|
||||
auto obj = json::parse(filename, source);
|
||||
for (auto& entry : Events::bindings) {
|
||||
auto& binding = entry.second;
|
||||
dynamic::Value SettingsHandler::getValue(const std::string& name) const {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
throw std::runtime_error("setting '"+name+"' does not exist");
|
||||
}
|
||||
auto setting = found->second;
|
||||
if (auto number = dynamic_cast<NumberSetting*>(setting)) {
|
||||
return static_cast<number_t>(number->get());
|
||||
} else if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
|
||||
return static_cast<integer_t>(integer->get());
|
||||
} else if (auto flag = dynamic_cast<FlagSetting*>(setting)) {
|
||||
return flag->get();
|
||||
} else if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
||||
return string->get();
|
||||
} else {
|
||||
throw std::runtime_error("type is not implemented for '"+name+"'");
|
||||
}
|
||||
}
|
||||
|
||||
auto jentry = obj->map(entry.first);
|
||||
if (jentry == nullptr)
|
||||
continue;
|
||||
inputtype type;
|
||||
std::string typestr;
|
||||
jentry->str("type", typestr);
|
||||
std::string SettingsHandler::toString(const std::string& name) const {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
throw std::runtime_error("setting '"+name+"' does not exist");
|
||||
}
|
||||
auto setting = found->second;
|
||||
return setting->toString();
|
||||
}
|
||||
|
||||
if (typestr == "keyboard") {
|
||||
type = inputtype::keyboard;
|
||||
} else if (typestr == "mouse") {
|
||||
type = inputtype::mouse;
|
||||
Setting* SettingsHandler::getSetting(const std::string& name) const {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
throw std::runtime_error("setting '"+name+"' does not exist");
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
bool SettingsHandler::has(const std::string& name) const {
|
||||
return map.find(name) != map.end();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void set_numeric_value(T* setting, const dynamic::Value& value) {
|
||||
if (auto num = std::get_if<integer_t>(&value)) {
|
||||
setting->set(*num);
|
||||
} else if (auto num = std::get_if<number_t>(&value)) {
|
||||
setting->set(*num);
|
||||
} else if (auto flag = std::get_if<bool>(&value)) {
|
||||
setting->set(*flag);
|
||||
} else {
|
||||
throw std::runtime_error("type error, numeric value expected");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsHandler::setValue(const std::string& name, const dynamic::Value& value) {
|
||||
auto found = map.find(name);
|
||||
if (found == map.end()) {
|
||||
throw std::runtime_error("setting '"+name+"' does not exist");
|
||||
}
|
||||
auto setting = found->second;
|
||||
if (auto number = dynamic_cast<NumberSetting*>(setting)) {
|
||||
set_numeric_value(number, value);
|
||||
} else if (auto integer = dynamic_cast<IntegerSetting*>(setting)) {
|
||||
set_numeric_value(integer, value);
|
||||
} else if (auto flag = dynamic_cast<FlagSetting*>(setting)) {
|
||||
set_numeric_value(flag, value);
|
||||
} else if (auto string = dynamic_cast<StringSetting*>(setting)) {
|
||||
if (auto num = std::get_if<integer_t>(&value)) {
|
||||
string->set(std::to_string(*num));
|
||||
} else if (auto num = std::get_if<number_t>(&value)) {
|
||||
string->set(std::to_string(*num));
|
||||
} else if (auto flag = std::get_if<bool>(&value)) {
|
||||
string->set(*flag ? "true" : "false");
|
||||
} else if (auto str = std::get_if<std::string>(&value)) {
|
||||
string->set(*str);
|
||||
} else {
|
||||
std::cerr << "unknown input type '" << typestr << "'" << std::endl;
|
||||
continue;
|
||||
throw std::runtime_error("not implemented for type");
|
||||
}
|
||||
binding.type = type;
|
||||
jentry->num("code", binding.code);
|
||||
} else {
|
||||
throw std::runtime_error("type is not implement - setting '"+name+"'");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Section>& SettingsHandler::getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef FILES_SETTINGS_IO_H_
|
||||
#define FILES_SETTINGS_IO_H_
|
||||
|
||||
#include <string>
|
||||
#include "../settings.h"
|
||||
|
||||
namespace toml {
|
||||
class Wrapper;
|
||||
}
|
||||
|
||||
extern std::string write_controls();
|
||||
extern toml::Wrapper* create_wrapper(EngineSettings& settings);
|
||||
extern void load_controls(std::string filename, std::string source);
|
||||
|
||||
#endif // FILES_SETTINGS_IO_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef FILES_SETTINGS_IO_HPP_
|
||||
#define FILES_SETTINGS_IO_HPP_
|
||||
|
||||
#include "../data/dynamic.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class Setting;
|
||||
struct EngineSettings;
|
||||
|
||||
struct Section {
|
||||
std::string name;
|
||||
std::vector<std::string> keys;
|
||||
};
|
||||
|
||||
class SettingsHandler {
|
||||
std::unordered_map<std::string, Setting*> map;
|
||||
std::vector<Section> sections;
|
||||
public:
|
||||
SettingsHandler(EngineSettings& settings);
|
||||
|
||||
dynamic::Value getValue(const std::string& name) const;
|
||||
void setValue(const std::string& name, const dynamic::Value& value);
|
||||
std::string toString(const std::string& name) const;
|
||||
Setting* getSetting(const std::string& name) const;
|
||||
bool has(const std::string& name) const;
|
||||
|
||||
std::vector<Section>& getSections();
|
||||
};
|
||||
|
||||
#endif // FILES_SETTINGS_IO_HPP_
|
||||
@@ -1,15 +1,17 @@
|
||||
#include "ContentGfxCache.h"
|
||||
#include "ContentGfxCache.hpp"
|
||||
|
||||
#include "UiDocument.hpp"
|
||||
|
||||
#include "../assets/Assets.hpp"
|
||||
#include "../content/Content.hpp"
|
||||
#include "../content/ContentPack.hpp"
|
||||
#include "../core_defs.hpp"
|
||||
#include "../graphics/core/Atlas.hpp"
|
||||
#include "../maths/UVRegion.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../assets/Assets.h"
|
||||
#include "../content/Content.h"
|
||||
#include "../content/ContentPack.h"
|
||||
#include "../graphics/Atlas.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../core_defs.h"
|
||||
#include "UiDocument.h"
|
||||
|
||||
ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : content(content) {
|
||||
auto indices = content->getIndices();
|
||||
sideregions = std::make_unique<UVRegion[]>(indices->countBlockDefs() * 6);
|
||||
@@ -39,14 +41,6 @@ ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : conte
|
||||
ContentGfxCache::~ContentGfxCache() {
|
||||
}
|
||||
|
||||
std::shared_ptr<UiDocument> ContentGfxCache::getLayout(const std::string& id) {
|
||||
auto found = layouts.find(id);
|
||||
if (found == layouts.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
const Content* ContentGfxCache::getContent() const {
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef FRONTEND_BLOCKS_GFX_CACHE_H_
|
||||
#define FRONTEND_BLOCKS_GFX_CACHE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "../graphics/UVRegion.h"
|
||||
#include "../typedefs.h"
|
||||
|
||||
class Content;
|
||||
class Assets;
|
||||
class UiDocument;
|
||||
|
||||
using uidocuments_map = std::unordered_map<std::string, std::shared_ptr<UiDocument>>;
|
||||
|
||||
class ContentGfxCache {
|
||||
const Content* content;
|
||||
// array of block sides uv regions (6 per block)
|
||||
std::unique_ptr<UVRegion[]> sideregions;
|
||||
// all loaded layouts
|
||||
uidocuments_map layouts;
|
||||
public:
|
||||
ContentGfxCache(const Content* content, Assets* assets);
|
||||
~ContentGfxCache();
|
||||
|
||||
inline const UVRegion& getRegion(blockid_t id, int side) const {
|
||||
return sideregions[id * 6 + side];
|
||||
}
|
||||
|
||||
std::shared_ptr<UiDocument> getLayout(const std::string& id);
|
||||
|
||||
const Content* getContent() const;
|
||||
};
|
||||
|
||||
#endif // FRONTEND_BLOCKS_GFX_CACHE_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef FRONTEND_BLOCKS_GFX_CACHE_HPP_
|
||||
#define FRONTEND_BLOCKS_GFX_CACHE_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Content;
|
||||
class Assets;
|
||||
struct UVRegion;
|
||||
|
||||
class ContentGfxCache {
|
||||
const Content* content;
|
||||
// array of block sides uv regions (6 per block)
|
||||
std::unique_ptr<UVRegion[]> sideregions;
|
||||
public:
|
||||
ContentGfxCache(const Content* content, Assets* assets);
|
||||
~ContentGfxCache();
|
||||
|
||||
inline const UVRegion& getRegion(blockid_t id, int side) const {
|
||||
return sideregions[id * 6 + side];
|
||||
}
|
||||
|
||||
const Content* getContent() const;
|
||||
};
|
||||
|
||||
#endif // FRONTEND_BLOCKS_GFX_CACHE_HPP_
|
||||
@@ -1,160 +0,0 @@
|
||||
#ifndef FRONTEND_INVENTORY_VIEW_H_
|
||||
#define FRONTEND_INVENTORY_VIEW_H_
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "../frontend/gui/UINode.h"
|
||||
#include "../frontend/gui/containers.h"
|
||||
#include "../frontend/gui/controls.h"
|
||||
#include "../items/ItemStack.h"
|
||||
#include "../typedefs.h"
|
||||
|
||||
class Assets;
|
||||
class GfxContext;
|
||||
class Content;
|
||||
class ContentIndices;
|
||||
class LevelFrontend;
|
||||
class Inventory;
|
||||
|
||||
namespace gui {
|
||||
class UiXmlReader;
|
||||
}
|
||||
|
||||
namespace scripting {
|
||||
class Environment;
|
||||
}
|
||||
|
||||
using slotcallback = std::function<void(uint, ItemStack&)>;
|
||||
|
||||
class InventoryInteraction {
|
||||
ItemStack grabbedItem;
|
||||
public:
|
||||
InventoryInteraction() = default;
|
||||
|
||||
ItemStack& getGrabbedItem() {
|
||||
return grabbedItem;
|
||||
}
|
||||
};
|
||||
|
||||
struct SlotLayout {
|
||||
int index;
|
||||
glm::vec2 position;
|
||||
bool background;
|
||||
bool itemSource;
|
||||
slotcallback updateFunc;
|
||||
slotcallback shareFunc;
|
||||
slotcallback rightClick;
|
||||
int padding = 0;
|
||||
|
||||
SlotLayout(
|
||||
int index,
|
||||
glm::vec2 position,
|
||||
bool background,
|
||||
bool itemSource,
|
||||
slotcallback updateFunc,
|
||||
slotcallback shareFunc,
|
||||
slotcallback rightClick
|
||||
);
|
||||
};
|
||||
|
||||
class SlotView : public gui::UINode {
|
||||
LevelFrontend* frontend = nullptr;
|
||||
InventoryInteraction* interaction = nullptr;
|
||||
const Content* content;
|
||||
SlotLayout layout;
|
||||
bool highlighted = false;
|
||||
|
||||
int64_t inventoryid = 0;
|
||||
ItemStack* bound = nullptr;
|
||||
public:
|
||||
SlotView(SlotLayout layout);
|
||||
|
||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||
|
||||
void setHighlighted(bool flag);
|
||||
bool isHighlighted() const;
|
||||
|
||||
virtual void clicked(gui::GUI*, mousecode) override;
|
||||
virtual void onFocus(gui::GUI*) override;
|
||||
|
||||
void bind(
|
||||
int64_t inventoryid,
|
||||
ItemStack& stack,
|
||||
LevelFrontend* frontend,
|
||||
InventoryInteraction* interaction
|
||||
);
|
||||
|
||||
const SlotLayout& getLayout() const;
|
||||
};
|
||||
|
||||
class InventoryView : public gui::Container {
|
||||
const Content* content;
|
||||
const ContentIndices* indices;
|
||||
|
||||
std::shared_ptr<Inventory> inventory;
|
||||
LevelFrontend* frontend = nullptr;
|
||||
InventoryInteraction* interaction = nullptr;
|
||||
|
||||
std::vector<SlotView*> slots;
|
||||
glm::vec2 origin {};
|
||||
public:
|
||||
InventoryView();
|
||||
virtual ~InventoryView();
|
||||
|
||||
void setInventory(std::shared_ptr<Inventory> inventory);
|
||||
|
||||
virtual void setPos(glm::vec2 pos) override;
|
||||
|
||||
void setOrigin(glm::vec2 origin);
|
||||
glm::vec2 getOrigin() const;
|
||||
|
||||
void setSelected(int index);
|
||||
|
||||
void bind(
|
||||
std::shared_ptr<Inventory> inventory,
|
||||
LevelFrontend* frontend,
|
||||
InventoryInteraction* interaction
|
||||
);
|
||||
|
||||
void unbind();
|
||||
|
||||
std::shared_ptr<SlotView> addSlot(SlotLayout layout);
|
||||
|
||||
std::shared_ptr<Inventory> getInventory() const;
|
||||
|
||||
size_t getSlotsCount() const;
|
||||
|
||||
static void createReaders(gui::UiXmlReader& reader);
|
||||
|
||||
static const int SLOT_INTERVAL = 4;
|
||||
static const int SLOT_SIZE = ITEM_ICON_SIZE;
|
||||
};
|
||||
|
||||
class InventoryBuilder {
|
||||
std::shared_ptr<InventoryView> view;
|
||||
public:
|
||||
InventoryBuilder();
|
||||
|
||||
/// @brief Add slots grid to inventory view
|
||||
/// @param cols grid columns
|
||||
/// @param count total number of grid slots
|
||||
/// @param pos position of the first slot of the grid
|
||||
/// @param padding additional space around the grid
|
||||
/// @param addpanel automatically create panel behind the grid
|
||||
/// with size including padding
|
||||
/// @param slotLayout slot settings (index and position are ignored)
|
||||
void addGrid(
|
||||
int cols, int count,
|
||||
glm::vec2 pos,
|
||||
int padding,
|
||||
bool addpanel,
|
||||
SlotLayout slotLayout
|
||||
);
|
||||
|
||||
void add(SlotLayout slotLayout);
|
||||
std::shared_ptr<InventoryView> build();
|
||||
};
|
||||
|
||||
#endif // FRONTEND_INVENTORY_VIEW_H_
|
||||
@@ -1,25 +1,27 @@
|
||||
#include "LevelFrontend.h"
|
||||
#include "LevelFrontend.hpp"
|
||||
|
||||
#include "BlocksPreview.h"
|
||||
#include "ContentGfxCache.h"
|
||||
#include "ContentGfxCache.hpp"
|
||||
|
||||
#include "../audio/audio.h"
|
||||
#include "../world/Level.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../assets/Assets.h"
|
||||
#include "../graphics/Atlas.h"
|
||||
#include "../content/Content.h"
|
||||
|
||||
#include "../logic/LevelController.h"
|
||||
#include "../logic/PlayerController.h"
|
||||
#include "../assets/Assets.hpp"
|
||||
#include "../audio/audio.hpp"
|
||||
#include "../content/Content.hpp"
|
||||
#include "../graphics/core/Atlas.hpp"
|
||||
#include "../graphics/render/BlocksPreview.hpp"
|
||||
#include "../logic/LevelController.hpp"
|
||||
#include "../logic/PlayerController.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
#include "../world/Level.hpp"
|
||||
|
||||
LevelFrontend::LevelFrontend(LevelController* controller, Assets* assets)
|
||||
: level(controller->getLevel()),
|
||||
controller(controller),
|
||||
assets(assets),
|
||||
contentCache(std::make_unique<ContentGfxCache>(level->content, assets)),
|
||||
blocksAtlas(BlocksPreview::build(contentCache.get(), assets, level->content))
|
||||
contentCache(std::make_unique<ContentGfxCache>(level->content, assets))
|
||||
{
|
||||
assets->store(
|
||||
BlocksPreview::build(contentCache.get(), assets, level->content).release(),
|
||||
"block-previews"
|
||||
);
|
||||
controller->getPlayerController()->listenBlockInteraction(
|
||||
[=](Player*, glm::ivec3 pos, const Block* def, BlockInteraction type) {
|
||||
auto material = level->content->findBlockMaterial(def->material);
|
||||
@@ -81,10 +83,6 @@ ContentGfxCache* LevelFrontend::getContentGfxCache() const {
|
||||
return contentCache.get();
|
||||
}
|
||||
|
||||
Atlas* LevelFrontend::getBlocksAtlas() const {
|
||||
return blocksAtlas.get();
|
||||
}
|
||||
|
||||
LevelController* LevelFrontend::getController() const {
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#ifndef FRONTEND_LEVEL_FRONTEND_H_
|
||||
#define FRONTEND_LEVEL_FRONTEND_H_
|
||||
#ifndef FRONTEND_LEVEL_FRONTEND_HPP_
|
||||
#define FRONTEND_LEVEL_FRONTEND_HPP_
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Atlas;
|
||||
class Level;
|
||||
class Assets;
|
||||
class BlocksPreview;
|
||||
class ContentGfxCache;
|
||||
class LevelController;
|
||||
|
||||
@@ -15,7 +13,6 @@ class LevelFrontend {
|
||||
LevelController* controller;
|
||||
Assets* assets;
|
||||
std::unique_ptr<ContentGfxCache> contentCache;
|
||||
std::unique_ptr<Atlas> blocksAtlas;
|
||||
public:
|
||||
LevelFrontend(LevelController* controller, Assets* assets);
|
||||
~LevelFrontend();
|
||||
@@ -23,9 +20,7 @@ public:
|
||||
Level* getLevel() const;
|
||||
Assets* getAssets() const;
|
||||
ContentGfxCache* getContentGfxCache() const;
|
||||
Atlas* getBlocksAtlas() const;
|
||||
|
||||
LevelController* getController() const;
|
||||
};
|
||||
|
||||
#endif // FRONTEND_LEVEL_FRONTEND_H_
|
||||
#endif // FRONTEND_LEVEL_FRONTEND_HPP_
|
||||
+31
-32
@@ -1,27 +1,32 @@
|
||||
#include "UiDocument.h"
|
||||
#include "UiDocument.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include "gui/UINode.h"
|
||||
#include "gui/containers.h"
|
||||
#include "InventoryView.h"
|
||||
#include "../logic/scripting/scripting.h"
|
||||
#include "../files/files.h"
|
||||
#include "../frontend/gui/gui_xml.h"
|
||||
#include "../files/files.hpp"
|
||||
#include "../graphics/ui/elements/UINode.hpp"
|
||||
#include "../graphics/ui/elements/InventoryView.hpp"
|
||||
#include "../graphics/ui/gui_xml.hpp"
|
||||
#include "../logic/scripting/scripting.hpp"
|
||||
|
||||
UiDocument::UiDocument(
|
||||
std::string id,
|
||||
uidocscript script,
|
||||
std::shared_ptr<gui::UINode> root,
|
||||
std::unique_ptr<scripting::Environment> env
|
||||
) : id(id), script(script), root(root), env(std::move(env)) {
|
||||
collect(map, root);
|
||||
scriptenv env
|
||||
) : id(id), script(script), root(root), env(env) {
|
||||
gui::UINode::getIndices(root, map);
|
||||
}
|
||||
|
||||
void UiDocument::rebuildIndices() {
|
||||
gui::UINode::getIndices(root, map);
|
||||
}
|
||||
|
||||
const uinodes_map& UiDocument::getMap() const {
|
||||
return map;
|
||||
}
|
||||
|
||||
uinodes_map& UiDocument::getMapWriteable() {
|
||||
return map;
|
||||
}
|
||||
|
||||
const std::string& UiDocument::getId() const {
|
||||
return id;
|
||||
}
|
||||
@@ -42,30 +47,19 @@ const uidocscript& UiDocument::getScript() const {
|
||||
return script;
|
||||
}
|
||||
|
||||
int UiDocument::getEnvironment() const {
|
||||
return env->getId();
|
||||
scriptenv UiDocument::getEnvironment() const {
|
||||
return env;
|
||||
}
|
||||
|
||||
void UiDocument::collect(uinodes_map& map, std::shared_ptr<gui::UINode> node) {
|
||||
const std::string& id = node->getId();
|
||||
if (!id.empty()) {
|
||||
map[id] = node;
|
||||
}
|
||||
auto container = dynamic_cast<gui::Container*>(node.get());
|
||||
if (container) {
|
||||
for (auto subnode : container->getNodes()) {
|
||||
collect(map, subnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<UiDocument> UiDocument::read(AssetsLoader& loader, int penv, std::string namesp, fs::path file) {
|
||||
std::unique_ptr<UiDocument> UiDocument::read(scriptenv penv, std::string name, fs::path file) {
|
||||
const std::string text = files::read_string(file);
|
||||
auto xmldoc = xml::parse(file.u8string(), text);
|
||||
|
||||
auto env = scripting::create_doc_environment(penv, namesp);
|
||||
gui::UiXmlReader reader(*env, loader);
|
||||
InventoryView::createReaders(reader);
|
||||
auto env = penv == nullptr
|
||||
? scripting::create_doc_environment(scripting::get_root_environment(), name)
|
||||
: scripting::create_doc_environment(penv, name);
|
||||
|
||||
gui::UiXmlReader reader(env);
|
||||
auto view = reader.readXML(
|
||||
file.u8string(), xmldoc->getRoot()
|
||||
);
|
||||
@@ -73,7 +67,12 @@ std::unique_ptr<UiDocument> UiDocument::read(AssetsLoader& loader, int penv, std
|
||||
uidocscript script {};
|
||||
auto scriptFile = fs::path(file.u8string()+".lua");
|
||||
if (fs::is_regular_file(scriptFile)) {
|
||||
scripting::load_layout_script(env->getId(), namesp, scriptFile, script);
|
||||
scripting::load_layout_script(env, name, scriptFile, script);
|
||||
}
|
||||
return std::make_unique<UiDocument>(namesp, script, view, std::move(env));
|
||||
return std::make_unique<UiDocument>(name, script, view, env);
|
||||
}
|
||||
|
||||
std::shared_ptr<gui::UINode> UiDocument::readElement(fs::path file) {
|
||||
auto document = read(nullptr, file.filename().u8string(), file);
|
||||
return document->getRoot();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#ifndef FRONTEND_UI_DOCUMENT_H_
|
||||
#define FRONTEND_UI_DOCUMENT_H_
|
||||
#ifndef FRONTEND_UI_DOCUMENT_HPP_
|
||||
#define FRONTEND_UI_DOCUMENT_HPP_
|
||||
|
||||
#include "../typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -12,44 +14,40 @@ namespace gui {
|
||||
class UINode;
|
||||
}
|
||||
|
||||
namespace scripting {
|
||||
class Environment;
|
||||
}
|
||||
|
||||
struct uidocscript {
|
||||
int environment;
|
||||
bool onopen : 1;
|
||||
bool onprogress : 1;
|
||||
bool onclose : 1;
|
||||
};
|
||||
|
||||
using uinodes_map = std::unordered_map<std::string, std::shared_ptr<gui::UINode>>;
|
||||
|
||||
class AssetsLoader;
|
||||
|
||||
class UiDocument {
|
||||
std::string id;
|
||||
uidocscript script;
|
||||
uinodes_map map;
|
||||
std::shared_ptr<gui::UINode> root;
|
||||
std::unique_ptr<scripting::Environment> env;
|
||||
scriptenv env;
|
||||
public:
|
||||
UiDocument(
|
||||
std::string id,
|
||||
uidocscript script,
|
||||
std::shared_ptr<gui::UINode> root,
|
||||
std::unique_ptr<scripting::Environment> env
|
||||
scriptenv env
|
||||
);
|
||||
|
||||
void rebuildIndices();
|
||||
|
||||
const std::string& getId() const;
|
||||
const uinodes_map& getMap() const;
|
||||
uinodes_map& getMapWriteable();
|
||||
const std::shared_ptr<gui::UINode> getRoot() const;
|
||||
const std::shared_ptr<gui::UINode> get(const std::string& id) const;
|
||||
const uidocscript& getScript() const;
|
||||
int getEnvironment() const;
|
||||
/* Collect map of all uinodes having identifiers */
|
||||
static void collect(uinodes_map& map, std::shared_ptr<gui::UINode> node);
|
||||
scriptenv getEnvironment() const;
|
||||
|
||||
static std::unique_ptr<UiDocument> read(AssetsLoader& loader, int env, std::string namesp, fs::path file);
|
||||
static std::unique_ptr<UiDocument> read(scriptenv parent_env, std::string name, fs::path file);
|
||||
static std::shared_ptr<gui::UINode> readElement(fs::path file);
|
||||
};
|
||||
|
||||
#endif // FRONTEND_UI_DOCUMENT_H_
|
||||
#endif // FRONTEND_UI_DOCUMENT_HPP_
|
||||
@@ -1,22 +1,25 @@
|
||||
#include "../audio/audio.hpp"
|
||||
#include "../delegates.hpp"
|
||||
#include "../engine.hpp"
|
||||
#include "../graphics/core/Mesh.hpp"
|
||||
#include "../graphics/ui/elements/CheckBox.hpp"
|
||||
#include "../graphics/ui/elements/TextBox.hpp"
|
||||
#include "../graphics/ui/elements/TrackBar.hpp"
|
||||
#include "../graphics/ui/elements/InputBindBox.hpp"
|
||||
#include "../graphics/render/WorldRenderer.hpp"
|
||||
#include "../objects/Player.hpp"
|
||||
#include "../physics/Hitbox.hpp"
|
||||
#include "../util/stringutil.hpp"
|
||||
#include "../voxels/Block.hpp"
|
||||
#include "../voxels/Chunk.hpp"
|
||||
#include "../voxels/Chunks.hpp"
|
||||
#include "../world/Level.hpp"
|
||||
#include "../world/World.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "gui/controls.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../graphics/Mesh.h"
|
||||
#include "../objects/Player.h"
|
||||
#include "../physics/Hitbox.h"
|
||||
#include "../world/Level.h"
|
||||
#include "../world/World.h"
|
||||
#include "../voxels/Chunks.h"
|
||||
#include "../voxels/Block.h"
|
||||
#include "../util/stringutil.h"
|
||||
#include "../delegates.h"
|
||||
#include "../engine.h"
|
||||
|
||||
#include "WorldRenderer.h"
|
||||
|
||||
using namespace gui;
|
||||
|
||||
static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
|
||||
@@ -60,7 +63,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
}));
|
||||
panel->add(create_label([=](){
|
||||
auto& settings = engine->getSettings();
|
||||
bool culling = settings.graphics.frustumCulling;
|
||||
bool culling = settings.graphics.frustumCulling.get();
|
||||
return L"frustum-culling: "+std::wstring(culling ? L"on" : L"off");
|
||||
}));
|
||||
panel->add(create_label([=]() {
|
||||
@@ -79,7 +82,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
L" "+stream.str();
|
||||
}));
|
||||
panel->add(create_label([=](){
|
||||
return L"seed: "+std::to_wstring(level->world->getSeed());
|
||||
return L"seed: "+std::to_wstring(level->getWorld()->getSeed());
|
||||
}));
|
||||
|
||||
for (int ax = 0; ax < 3; ax++) {
|
||||
@@ -95,6 +98,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
|
||||
// Coord input
|
||||
auto box = std::make_shared<TextBox>(L"");
|
||||
auto boxRef = box.get();
|
||||
box->setTextSupplier([=]() {
|
||||
Hitbox* hitbox = player->hitbox.get();
|
||||
return util::to_wstring(hitbox->position[ax], 2);
|
||||
@@ -109,7 +113,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
});
|
||||
box->setOnEditStart([=](){
|
||||
Hitbox* hitbox = player->hitbox.get();
|
||||
box->setText(std::to_wstring(int(hitbox->position[ax])));
|
||||
boxRef->setText(std::to_wstring(int(hitbox->position[ax])));
|
||||
});
|
||||
box->setSize(glm::vec2(230, 27));
|
||||
|
||||
@@ -118,7 +122,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
}
|
||||
panel->add(create_label([=](){
|
||||
int hour, minute, second;
|
||||
timeutil::from_value(level->world->daytime, hour, minute, second);
|
||||
timeutil::from_value(level->getWorld()->daytime, hour, minute, second);
|
||||
|
||||
std::wstring timeString =
|
||||
util::lfill(std::to_wstring(hour), 2, L'0') + L":" +
|
||||
@@ -127,8 +131,8 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
}));
|
||||
{
|
||||
auto bar = std::make_shared<TrackBar>(0.0f, 1.0f, 1.0f, 0.005f, 8);
|
||||
bar->setSupplier([=]() {return level->world->daytime;});
|
||||
bar->setConsumer([=](double val) {level->world->daytime = val;});
|
||||
bar->setSupplier([=]() {return level->getWorld()->daytime;});
|
||||
bar->setConsumer([=](double val) {level->getWorld()->daytime = val;});
|
||||
panel->add(bar);
|
||||
}
|
||||
{
|
||||
@@ -142,10 +146,10 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
L"Show Chunk Borders", glm::vec2(400, 24)
|
||||
);
|
||||
checkbox->setSupplier([=]() {
|
||||
return engine->getSettings().debug.showChunkBorders;
|
||||
return WorldRenderer::showChunkBorders;
|
||||
});
|
||||
checkbox->setConsumer([=](bool checked) {
|
||||
engine->getSettings().debug.showChunkBorders = checked;
|
||||
WorldRenderer::showChunkBorders = checked;
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
|
||||
@@ -1,472 +0,0 @@
|
||||
#include "BlocksRenderer.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "../../graphics/Mesh.h"
|
||||
#include "../../graphics/UVRegion.h"
|
||||
#include "../../constants.h"
|
||||
#include "../../content/Content.h"
|
||||
#include "../../voxels/Block.h"
|
||||
#include "../../voxels/Chunk.h"
|
||||
#include "../../voxels/VoxelsVolume.h"
|
||||
#include "../../voxels/ChunksStorage.h"
|
||||
#include "../../lighting/Lightmap.h"
|
||||
#include "../../frontend/ContentGfxCache.h"
|
||||
|
||||
using glm::ivec3;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
|
||||
const uint BlocksRenderer::VERTEX_SIZE = 6;
|
||||
const vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f);
|
||||
|
||||
BlocksRenderer::BlocksRenderer(size_t capacity,
|
||||
const Content* content,
|
||||
const ContentGfxCache* cache,
|
||||
const EngineSettings& settings)
|
||||
: content(content),
|
||||
vertexOffset(0),
|
||||
indexOffset(0),
|
||||
indexSize(0),
|
||||
capacity(capacity),
|
||||
cache(cache),
|
||||
settings(settings) {
|
||||
vertexBuffer = new float[capacity];
|
||||
indexBuffer = new int[capacity];
|
||||
voxelsBuffer = new VoxelsVolume(CHUNK_W + 2, CHUNK_H, CHUNK_D + 2);
|
||||
blockDefsCache = content->getIndices()->getBlockDefs();
|
||||
}
|
||||
|
||||
BlocksRenderer::~BlocksRenderer() {
|
||||
delete voxelsBuffer;
|
||||
delete[] vertexBuffer;
|
||||
delete[] indexBuffer;
|
||||
}
|
||||
|
||||
/* Basic vertex add method */
|
||||
void BlocksRenderer::vertex(const vec3& coord, float u, float v, const vec4& light) {
|
||||
vertexBuffer[vertexOffset++] = coord.x;
|
||||
vertexBuffer[vertexOffset++] = coord.y;
|
||||
vertexBuffer[vertexOffset++] = coord.z;
|
||||
|
||||
vertexBuffer[vertexOffset++] = u;
|
||||
vertexBuffer[vertexOffset++] = v;
|
||||
|
||||
union {
|
||||
float floating;
|
||||
uint32_t integer;
|
||||
} compressed;
|
||||
|
||||
compressed.integer = (uint32_t(light.r * 255) & 0xff) << 24;
|
||||
compressed.integer |= (uint32_t(light.g * 255) & 0xff) << 16;
|
||||
compressed.integer |= (uint32_t(light.b * 255) & 0xff) << 8;
|
||||
compressed.integer |= (uint32_t(light.a * 255) & 0xff);
|
||||
|
||||
vertexBuffer[vertexOffset++] = compressed.floating;
|
||||
}
|
||||
|
||||
void BlocksRenderer::index(int a, int b, int c, int d, int e, int f) {
|
||||
indexBuffer[indexSize++] = indexOffset + a;
|
||||
indexBuffer[indexSize++] = indexOffset + b;
|
||||
indexBuffer[indexSize++] = indexOffset + c;
|
||||
indexBuffer[indexSize++] = indexOffset + d;
|
||||
indexBuffer[indexSize++] = indexOffset + e;
|
||||
indexBuffer[indexSize++] = indexOffset + f;
|
||||
indexOffset += 4;
|
||||
}
|
||||
|
||||
/* Add face with precalculated lights */
|
||||
void BlocksRenderer::face(const vec3& coord,
|
||||
float w, float h, float d,
|
||||
const vec3& axisX,
|
||||
const vec3& axisY,
|
||||
const vec3& axisZ,
|
||||
const UVRegion& region,
|
||||
const vec4(&lights)[4],
|
||||
const vec4& tint) {
|
||||
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * 4 > capacity) {
|
||||
overflow = true;
|
||||
return;
|
||||
}
|
||||
vec3 X = axisX * w;
|
||||
vec3 Y = axisY * h;
|
||||
vec3 Z = axisZ * d;
|
||||
float s = 0.5f;
|
||||
vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, lights[0] * tint);
|
||||
vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, lights[1] * tint);
|
||||
vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, lights[2] * tint);
|
||||
vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, lights[3] * tint);
|
||||
index(0, 1, 3, 1, 2, 3);
|
||||
}
|
||||
|
||||
void BlocksRenderer::vertex(const vec3& coord,
|
||||
float u, float v,
|
||||
const vec4& tint,
|
||||
const vec3& axisX,
|
||||
const vec3& axisY,
|
||||
const vec3& axisZ) {
|
||||
vec3 pos = coord+axisZ*0.5f+(axisX+axisY)*0.5f;
|
||||
vec4 light = pickSoftLight(ivec3(round(pos.x), round(pos.y), round(pos.z)), axisX, axisY);
|
||||
vertex(coord, u, v, light * tint);
|
||||
}
|
||||
|
||||
void BlocksRenderer::face(const vec3& coord,
|
||||
const vec3& X,
|
||||
const vec3& Y,
|
||||
const vec3& Z,
|
||||
const UVRegion& region,
|
||||
bool lights) {
|
||||
if (vertexOffset + BlocksRenderer::VERTEX_SIZE * 4 > capacity) {
|
||||
overflow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
float s = 0.5f;
|
||||
if (lights) {
|
||||
float d = glm::dot(Z, SUN_VECTOR);
|
||||
d = 0.8f + d * 0.2f;
|
||||
|
||||
vec3 axisX = glm::normalize(X);
|
||||
vec3 axisY = glm::normalize(Y);
|
||||
vec3 axisZ = glm::normalize(Z);
|
||||
|
||||
vec4 tint(d);
|
||||
vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, tint, axisX, axisY, axisZ);
|
||||
vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, tint, axisX, axisY, axisZ);
|
||||
vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, tint, axisX, axisY, axisZ);
|
||||
vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, tint, axisX, axisY, axisZ);
|
||||
} else {
|
||||
vec4 tint(1.0f);
|
||||
vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, tint);
|
||||
vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, tint);
|
||||
vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, tint);
|
||||
vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, tint);
|
||||
}
|
||||
index(0, 1, 2, 0, 2, 3);
|
||||
}
|
||||
|
||||
void BlocksRenderer::tetragonicFace(const vec3& coord, const vec3& p1,
|
||||
const vec3& p2, const vec3& p3, const vec3& p4,
|
||||
const vec3& X,
|
||||
const vec3& Y,
|
||||
const vec3& Z,
|
||||
const UVRegion& texreg,
|
||||
bool lights) {
|
||||
|
||||
const vec3 fp1 = (p1.x - 0.5f) * X + (p1.y - 0.5f) * Y + (p1.z - 0.5f) * Z;
|
||||
const vec3 fp2 = (p2.x - 0.5f) * X + (p2.y - 0.5f) * Y + (p2.z - 0.5f) * Z;
|
||||
const vec3 fp3 = (p3.x - 0.5f) * X + (p3.y - 0.5f) * Y + (p3.z - 0.5f) * Z;
|
||||
const vec3 fp4 = (p4.x - 0.5f) * X + (p4.y - 0.5f) * Y + (p4.z - 0.5f) * Z;
|
||||
|
||||
vec4 tint(1.0f);
|
||||
if (lights) {
|
||||
vec3 dir = glm::cross(fp2 - fp1, fp3 - fp1);
|
||||
vec3 normal = glm::normalize(dir);
|
||||
|
||||
float d = glm::dot(normal, SUN_VECTOR);
|
||||
d = 0.8f + d * 0.2f;
|
||||
tint *= d;
|
||||
tint *= pickLight(coord);
|
||||
// debug normal
|
||||
// tint.x = normal.x * 0.5f + 0.5f;
|
||||
// tint.y = normal.y * 0.5f + 0.5f;
|
||||
// tint.z = normal.z * 0.5f + 0.5f;
|
||||
}
|
||||
vertex(coord + fp1, texreg.u1, texreg.v1, tint);
|
||||
vertex(coord + fp2, texreg.u2, texreg.v1, tint);
|
||||
vertex(coord + fp3, texreg.u2, texreg.v2, tint);
|
||||
vertex(coord + fp4, texreg.u1, texreg.v2, tint);
|
||||
index(0, 1, 3, 1, 2, 3);
|
||||
}
|
||||
|
||||
void BlocksRenderer::blockXSprite(int x, int y, int z,
|
||||
const vec3& size,
|
||||
const UVRegion& texface1,
|
||||
const UVRegion& texface2,
|
||||
float spread) {
|
||||
vec4 lights[]{
|
||||
pickSoftLight({x, y + 1, z}, {1, 0, 0}, {0, 1, 0}),
|
||||
pickSoftLight({x + 1, y + 1, z}, {1, 0, 0}, {0, 1, 0}),
|
||||
pickSoftLight({x + 1, y + 1, z}, {1, 0, 0}, {0, 1, 0}),
|
||||
pickSoftLight({x, y + 1, z}, {1, 0, 0}, {0, 1, 0}) };
|
||||
|
||||
int rand = ((x * z + y) ^ (z * y - x)) * (z + y);
|
||||
|
||||
float xs = ((float)(char)rand / 512) * spread;
|
||||
float zs = ((float)(char)(rand >> 8) / 512) * spread;
|
||||
|
||||
const float w = size.x / 1.41f;
|
||||
const float tint = 0.8f;
|
||||
|
||||
face(vec3(x + xs, y, z + zs),
|
||||
w, size.y, 0, vec3(1, 0, 1), vec3(0, 1, 0), vec3(),
|
||||
texface1, lights, vec4(tint));
|
||||
face(vec3(x + xs, y, z + zs),
|
||||
w, size.y, 0, vec3(-1, 0, -1), vec3(0, 1, 0), vec3(),
|
||||
texface1, lights, vec4(tint));
|
||||
|
||||
face(vec3(x + xs, y, z + zs),
|
||||
w, size.y, 0, vec3(1, 0, -1), vec3(0, 1, 0), vec3(),
|
||||
texface1, lights, vec4(tint));
|
||||
face(vec3(x + xs, y, z + zs),
|
||||
w, size.y, 0, vec3(-1, 0, 1), vec3(0, 1, 0), vec3(),
|
||||
texface1, lights, vec4(tint));
|
||||
}
|
||||
|
||||
// HINT: texture faces order: {east, west, bottom, top, south, north}
|
||||
|
||||
/* AABB blocks render method */
|
||||
void BlocksRenderer::blockAABB(const ivec3& icoord,
|
||||
const UVRegion(&texfaces)[6],
|
||||
const Block* block, ubyte rotation,
|
||||
bool lights) {
|
||||
if (block->hitboxes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AABB hitbox = block->hitboxes[0];
|
||||
for (const auto& box : block->hitboxes) {
|
||||
hitbox.a = glm::min(hitbox.a, box.a);
|
||||
hitbox.b = glm::max(hitbox.b, box.b);
|
||||
}
|
||||
|
||||
vec3 size = hitbox.size();
|
||||
vec3 X(1, 0, 0);
|
||||
vec3 Y(0, 1, 0);
|
||||
vec3 Z(0, 0, 1);
|
||||
vec3 coord(icoord);
|
||||
if (block->rotatable) {
|
||||
auto& rotations = block->rotations;
|
||||
auto& orient = rotations.variants[rotation];
|
||||
X = orient.axisX;
|
||||
Y = orient.axisY;
|
||||
Z = orient.axisZ;
|
||||
orient.transform(hitbox);
|
||||
}
|
||||
|
||||
coord = vec3(icoord) - vec3(0.5f) + hitbox.center();
|
||||
|
||||
face(coord, X*size.x, Y*size.y, Z*size.z, texfaces[5], lights); // north
|
||||
face(coord, -X*size.x, Y*size.y, -Z*size.z, texfaces[4], lights); // south
|
||||
|
||||
face(coord, X*size.x, -Z*size.z, Y*size.y, texfaces[3], lights); // top
|
||||
face(coord, -X*size.x, -Z*size.z, -Y*size.y, texfaces[2], lights); // bottom
|
||||
|
||||
face(coord, -Z*size.z, Y*size.y, X*size.x, texfaces[1], lights); // west
|
||||
face(coord, Z*size.z, Y*size.y, -X*size.x, texfaces[0], lights); // east
|
||||
}
|
||||
|
||||
void BlocksRenderer::blockCustomModel(const ivec3& icoord,
|
||||
const Block* block, ubyte rotation, bool lights) {
|
||||
vec3 X(1, 0, 0);
|
||||
vec3 Y(0, 1, 0);
|
||||
vec3 Z(0, 0, 1);
|
||||
CoordSystem orient(X,Y,Z);
|
||||
vec3 coord(icoord);
|
||||
if (block->rotatable) {
|
||||
auto& rotations = block->rotations;
|
||||
orient = rotations.variants[rotation];
|
||||
X = orient.axisX;
|
||||
Y = orient.axisY;
|
||||
Z = orient.axisZ;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < block->modelBoxes.size(); i++) {
|
||||
AABB box = block->modelBoxes[i];
|
||||
vec3 size = box.size();
|
||||
if (block->rotatable) {
|
||||
orient.transform(box);
|
||||
}
|
||||
vec3 center_coord = coord - vec3(0.5f) + box.center();
|
||||
face(center_coord, X * size.x, Y * size.y, Z * size.z, block->modelUVs[i * 6 + 5], lights); // north
|
||||
face(center_coord, -X * size.x, Y * size.y, -Z * size.z, block->modelUVs[i * 6 + 4], lights); // south
|
||||
face(center_coord, X * size.x, -Z * size.z, Y * size.y, block->modelUVs[i * 6 + 3], lights); // top
|
||||
face(center_coord, -X * size.x, -Z * size.z, -Y * size.y, block->modelUVs[i * 6 + 2], lights); // bottom
|
||||
face(center_coord, -Z * size.z, Y * size.y, X * size.x, block->modelUVs[i * 6 + 1], lights); // west
|
||||
face(center_coord, Z * size.z, Y * size.y, -X * size.x, block->modelUVs[i * 6 + 0], lights); // east
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < block->modelExtraPoints.size()/4; i++) {
|
||||
tetragonicFace(coord,
|
||||
block->modelExtraPoints[i * 4 + 0],
|
||||
block->modelExtraPoints[i * 4 + 1],
|
||||
block->modelExtraPoints[i * 4 + 2],
|
||||
block->modelExtraPoints[i * 4 + 3],
|
||||
X, Y, Z,
|
||||
block->modelUVs[block->modelBoxes.size()*6 + i], lights);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fastest solid shaded blocks render method */
|
||||
void BlocksRenderer::blockCube(int x, int y, int z,
|
||||
const UVRegion(&texfaces)[6],
|
||||
const Block* block,
|
||||
ubyte states,
|
||||
bool lights) {
|
||||
ubyte group = block->drawGroup;
|
||||
|
||||
vec3 X(1, 0, 0);
|
||||
vec3 Y(0, 1, 0);
|
||||
vec3 Z(0, 0, 1);
|
||||
vec3 coord(x, y, z);
|
||||
if (block->rotatable) {
|
||||
auto& rotations = block->rotations;
|
||||
auto& orient = rotations.variants[states & BLOCK_ROT_MASK];
|
||||
X = orient.axisX;
|
||||
Y = orient.axisY;
|
||||
Z = orient.axisZ;
|
||||
}
|
||||
|
||||
if (isOpen(x+Z.x, y+Z.y, z+Z.z, group)) {
|
||||
face(coord, X, Y, Z, texfaces[5], lights);
|
||||
}
|
||||
if (isOpen(x-Z.x, y-Z.y, z-Z.z, group)) {
|
||||
face(coord, -X, Y, -Z, texfaces[4], lights);
|
||||
}
|
||||
if (isOpen(x+Y.x, y+Y.y, z+Y.z, group)) {
|
||||
face(coord, X, -Z, Y, texfaces[3], lights);
|
||||
}
|
||||
if (isOpen(x-Y.x, y-Y.y, z-Y.z, group)) {
|
||||
face(coord, X, Z, -Y, texfaces[2], lights);
|
||||
}
|
||||
if (isOpen(x+X.x, y+X.y, z+X.z, group)) {
|
||||
face(coord, -Z, Y, X, texfaces[1], lights);
|
||||
}
|
||||
if (isOpen(x-X.x, y-X.y, z-X.z, group)) {
|
||||
face(coord, Z, Y, -X, texfaces[0], lights);
|
||||
}
|
||||
}
|
||||
|
||||
// Does block allow to see other blocks sides (is it transparent)
|
||||
bool BlocksRenderer::isOpen(int x, int y, int z, ubyte group) const {
|
||||
blockid_t id = voxelsBuffer->pickBlockId(chunk->x * CHUNK_W + x,
|
||||
y,
|
||||
chunk->z * CHUNK_D + z);
|
||||
if (id == BLOCK_VOID)
|
||||
return false;
|
||||
const Block& block = *blockDefsCache[id];
|
||||
if ((block.drawGroup != group && block.lightPassing) || !block.rt.solid) {
|
||||
return true;
|
||||
}
|
||||
return !id;
|
||||
}
|
||||
|
||||
bool BlocksRenderer::isOpenForLight(int x, int y, int z) const {
|
||||
blockid_t id = voxelsBuffer->pickBlockId(chunk->x * CHUNK_W + x,
|
||||
y,
|
||||
chunk->z * CHUNK_D + z);
|
||||
if (id == BLOCK_VOID)
|
||||
return false;
|
||||
const Block& block = *blockDefsCache[id];
|
||||
if (block.lightPassing) {
|
||||
return true;
|
||||
}
|
||||
return !id;
|
||||
}
|
||||
|
||||
vec4 BlocksRenderer::pickLight(int x, int y, int z) const {
|
||||
if (isOpenForLight(x, y, z)) {
|
||||
light_t light = voxelsBuffer->pickLight(chunk->x * CHUNK_W + x,
|
||||
y,
|
||||
chunk->z * CHUNK_D + z);
|
||||
return vec4(Lightmap::extract(light, 0) / 15.0f,
|
||||
Lightmap::extract(light, 1) / 15.0f,
|
||||
Lightmap::extract(light, 2) / 15.0f,
|
||||
Lightmap::extract(light, 3) / 15.0f);
|
||||
}
|
||||
else {
|
||||
return vec4(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
vec4 BlocksRenderer::pickLight(const ivec3& coord) const {
|
||||
return pickLight(coord.x, coord.y, coord.z);
|
||||
}
|
||||
|
||||
vec4 BlocksRenderer::pickSoftLight(const ivec3& coord,
|
||||
const ivec3& right,
|
||||
const ivec3& up) const {
|
||||
return (
|
||||
pickLight(coord) +
|
||||
pickLight(coord - right) +
|
||||
pickLight(coord - right - up) +
|
||||
pickLight(coord - up)) * 0.25f;
|
||||
}
|
||||
|
||||
vec4 BlocksRenderer::pickSoftLight(float x, float y, float z,
|
||||
const ivec3& right,
|
||||
const ivec3& up) const {
|
||||
return pickSoftLight({int(round(x)), int(round(y)), int(round(z))}, right, up);
|
||||
}
|
||||
|
||||
void BlocksRenderer::render(const voxel* voxels) {
|
||||
int begin = chunk->bottom * (CHUNK_W * CHUNK_D);
|
||||
int end = chunk->top * (CHUNK_W * CHUNK_D);
|
||||
for (const auto drawGroup : *content->drawGroups) {
|
||||
for (int i = begin; i < end; i++) {
|
||||
const voxel& vox = voxels[i];
|
||||
blockid_t id = vox.id;
|
||||
const Block& def = *blockDefsCache[id];
|
||||
if (id == 0 || def.drawGroup != drawGroup)
|
||||
continue;
|
||||
const UVRegion texfaces[6]{ cache->getRegion(id, 0),
|
||||
cache->getRegion(id, 1),
|
||||
cache->getRegion(id, 2),
|
||||
cache->getRegion(id, 3),
|
||||
cache->getRegion(id, 4),
|
||||
cache->getRegion(id, 5)};
|
||||
int x = i % CHUNK_W;
|
||||
int y = i / (CHUNK_D * CHUNK_W);
|
||||
int z = (i / CHUNK_D) % CHUNK_W;
|
||||
switch (def.model) {
|
||||
case BlockModel::block:
|
||||
blockCube(x, y, z, texfaces, &def, vox.states, !def.rt.emissive);
|
||||
break;
|
||||
case BlockModel::xsprite: {
|
||||
blockXSprite(x, y, z, vec3(1.0f),
|
||||
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
|
||||
break;
|
||||
}
|
||||
case BlockModel::aabb: {
|
||||
blockAABB(ivec3(x,y,z), texfaces, &def, vox.rotation(), !def.rt.emissive);
|
||||
break;
|
||||
}
|
||||
case BlockModel::custom: {
|
||||
blockCustomModel(ivec3(x, y, z), &def, vox.rotation(), !def.rt.emissive);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (overflow)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlocksRenderer::build(const Chunk* chunk, const ChunksStorage* chunks) {
|
||||
this->chunk = chunk;
|
||||
voxelsBuffer->setPosition(chunk->x * CHUNK_W - 1, 0, chunk->z * CHUNK_D - 1);
|
||||
chunks->getVoxels(voxelsBuffer, settings.graphics.backlight);
|
||||
overflow = false;
|
||||
vertexOffset = 0;
|
||||
indexOffset = indexSize = 0;
|
||||
const voxel* voxels = chunk->voxels;
|
||||
render(voxels);
|
||||
}
|
||||
|
||||
Mesh* BlocksRenderer::createMesh() {
|
||||
const vattr attrs[]{ {3}, {2}, {1}, {0} };
|
||||
size_t vcount = vertexOffset / BlocksRenderer::VERTEX_SIZE;
|
||||
Mesh* mesh = new Mesh(vertexBuffer, vcount, indexBuffer, indexSize, attrs);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Mesh* BlocksRenderer::render(const Chunk* chunk, const ChunksStorage* chunks) {
|
||||
build(chunk, chunks);
|
||||
return createMesh();
|
||||
}
|
||||
|
||||
VoxelsVolume* BlocksRenderer::getVoxelsBuffer() const {
|
||||
return voxelsBuffer;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#ifndef GRAPHICS_BLOCKS_RENDERER_H
|
||||
#define GRAPHICS_BLOCKS_RENDERER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "../../graphics/UVRegion.h"
|
||||
#include "../../voxels/voxel.h"
|
||||
#include "../../typedefs.h"
|
||||
#include "../../settings.h"
|
||||
|
||||
class Content;
|
||||
class Mesh;
|
||||
class Block;
|
||||
class Chunk;
|
||||
class Chunks;
|
||||
class VoxelsVolume;
|
||||
class ChunksStorage;
|
||||
class ContentGfxCache;
|
||||
|
||||
class BlocksRenderer {
|
||||
static const glm::vec3 SUN_VECTOR;
|
||||
static const uint VERTEX_SIZE;
|
||||
const Content* const content;
|
||||
float* vertexBuffer;
|
||||
int* indexBuffer;
|
||||
size_t vertexOffset;
|
||||
size_t indexOffset, indexSize;
|
||||
size_t capacity;
|
||||
|
||||
bool overflow = false;
|
||||
|
||||
const Chunk* chunk = nullptr;
|
||||
VoxelsVolume* voxelsBuffer;
|
||||
|
||||
const Block* const* blockDefsCache;
|
||||
const ContentGfxCache* const cache;
|
||||
const EngineSettings& settings;
|
||||
|
||||
void vertex(const glm::vec3& coord, float u, float v, const glm::vec4& light);
|
||||
void index(int a, int b, int c, int d, int e, int f);
|
||||
|
||||
void vertex(const glm::vec3& coord, float u, float v,
|
||||
const glm::vec4& brightness,
|
||||
const glm::vec3& axisX,
|
||||
const glm::vec3& axisY,
|
||||
const glm::vec3& axisZ);
|
||||
|
||||
void face(const glm::vec3& coord, float w, float h, float d,
|
||||
const glm::vec3& axisX,
|
||||
const glm::vec3& axisY,
|
||||
const glm::vec3& axisZ,
|
||||
const UVRegion& region,
|
||||
const glm::vec4(&lights)[4],
|
||||
const glm::vec4& tint);
|
||||
|
||||
void face(const glm::vec3& coord,
|
||||
const glm::vec3& axisX,
|
||||
const glm::vec3& axisY,
|
||||
const glm::vec3& axisZ,
|
||||
const UVRegion& region,
|
||||
bool lights);
|
||||
|
||||
void tetragonicFace(const glm::vec3& coord,
|
||||
const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec3& p3, const glm::vec3& p4,
|
||||
const glm::vec3& X,
|
||||
const glm::vec3& Y,
|
||||
const glm::vec3& Z,
|
||||
const UVRegion& texreg,
|
||||
bool lights);
|
||||
|
||||
void blockCube(int x, int y, int z, const UVRegion(&faces)[6], const Block* block, ubyte states, bool lights);
|
||||
void blockAABB(const glm::ivec3& coord,
|
||||
const UVRegion(&faces)[6],
|
||||
const Block* block,
|
||||
ubyte rotation,
|
||||
bool lights);
|
||||
void blockXSprite(int x, int y, int z, const glm::vec3& size, const UVRegion& face1, const UVRegion& face2, float spread);
|
||||
void blockCustomModel(const glm::ivec3& icoord,
|
||||
const Block* block, ubyte rotation,
|
||||
bool lights);
|
||||
|
||||
bool isOpenForLight(int x, int y, int z) const;
|
||||
bool isOpen(int x, int y, int z, ubyte group) const;
|
||||
|
||||
glm::vec4 pickLight(int x, int y, int z) const;
|
||||
glm::vec4 pickLight(const glm::ivec3& coord) const;
|
||||
glm::vec4 pickSoftLight(const glm::ivec3& coord, const glm::ivec3& right, const glm::ivec3& up) const;
|
||||
glm::vec4 pickSoftLight(float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up) const;
|
||||
void render(const voxel* voxels);
|
||||
public:
|
||||
BlocksRenderer(size_t capacity, const Content* content, const ContentGfxCache* cache, const EngineSettings& settings);
|
||||
virtual ~BlocksRenderer();
|
||||
|
||||
void build(const Chunk* chunk, const ChunksStorage* chunks);
|
||||
Mesh* render(const Chunk* chunk, const ChunksStorage* chunks);
|
||||
Mesh* createMesh();
|
||||
VoxelsVolume* getVoxelsBuffer() const;
|
||||
};
|
||||
|
||||
#endif // GRAPHICS_BLOCKS_RENDERER_H
|
||||
@@ -1,150 +0,0 @@
|
||||
#include "ChunksRenderer.h"
|
||||
|
||||
#include "../../graphics/Mesh.h"
|
||||
#include "BlocksRenderer.h"
|
||||
#include "../../voxels/Chunk.h"
|
||||
#include "../../world/Level.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
|
||||
ChunksRenderer::ChunksRenderer(Level* level, const ContentGfxCache* cache, const EngineSettings& settings)
|
||||
: level(level), cache(cache), settings(settings) {
|
||||
const int MAX_FULL_CUBES = 3000;
|
||||
renderer = std::make_unique<BlocksRenderer>(
|
||||
9 * 6 * 6 * MAX_FULL_CUBES, level->content, cache, settings
|
||||
);
|
||||
|
||||
const uint num_threads = std::thread::hardware_concurrency();
|
||||
for (uint i = 0; i < num_threads; i++) {
|
||||
threads.emplace_back(&ChunksRenderer::threadLoop, this, i);
|
||||
workersBlocked.emplace_back();
|
||||
}
|
||||
std::cout << "created " << num_threads << " chunks rendering threads" << std::endl;
|
||||
}
|
||||
|
||||
ChunksRenderer::~ChunksRenderer() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(jobsMutex);
|
||||
working = false;
|
||||
}
|
||||
|
||||
resultsMutex.lock();
|
||||
while (!results.empty()) {
|
||||
mesh_entry entry = results.front();
|
||||
results.pop();
|
||||
entry.locked = false;
|
||||
entry.variable.notify_all();
|
||||
}
|
||||
resultsMutex.unlock();
|
||||
|
||||
jobsMutexCondition.notify_all();
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ChunksRenderer::threadLoop(int index) {
|
||||
const int MAX_FULL_CUBES = 3000;
|
||||
BlocksRenderer renderer(
|
||||
9 * 6 * 6 * MAX_FULL_CUBES, level->content, cache, settings
|
||||
);
|
||||
|
||||
std::condition_variable variable;
|
||||
std::mutex mutex;
|
||||
bool locked = false;
|
||||
while (working) {
|
||||
std::shared_ptr<Chunk> chunk;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(jobsMutex);
|
||||
jobsMutexCondition.wait(lock, [this] {
|
||||
return !jobs.empty() || !working;
|
||||
});
|
||||
if (!working) {
|
||||
break;
|
||||
}
|
||||
chunk = jobs.front();
|
||||
jobs.pop();
|
||||
}
|
||||
process(chunk, renderer);
|
||||
{
|
||||
resultsMutex.lock();
|
||||
results.push(mesh_entry {renderer, variable, index, locked, glm::ivec2(chunk->x, chunk->z)});
|
||||
locked = true;
|
||||
resultsMutex.unlock();
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
variable.wait(lock, [&] {
|
||||
return !working || !locked;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChunksRenderer::process(std::shared_ptr<Chunk> chunk, BlocksRenderer& renderer) {
|
||||
renderer.build(chunk.get(), level->chunksStorage.get());
|
||||
}
|
||||
|
||||
std::shared_ptr<Mesh> ChunksRenderer::render(std::shared_ptr<Chunk> chunk, bool important) {
|
||||
chunk->setModified(false);
|
||||
|
||||
if (important) {
|
||||
Mesh* mesh = renderer->render(chunk.get(), level->chunksStorage.get());
|
||||
auto sptr = std::shared_ptr<Mesh>(mesh);
|
||||
meshes[glm::ivec2(chunk->x, chunk->z)] = sptr;
|
||||
return sptr;
|
||||
}
|
||||
|
||||
glm::ivec2 key(chunk->x, chunk->z);
|
||||
if (inwork.find(key) != inwork.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inwork[key] = true;
|
||||
jobsMutex.lock();
|
||||
jobs.push(chunk);
|
||||
jobsMutex.unlock();
|
||||
jobsMutexCondition.notify_one();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChunksRenderer::unload(Chunk* chunk) {
|
||||
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
|
||||
if (found != meshes.end()) {
|
||||
meshes.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Mesh> ChunksRenderer::getOrRender(std::shared_ptr<Chunk> chunk, bool important) {
|
||||
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
|
||||
if (found != meshes.end()){
|
||||
if (chunk->isModified()) {
|
||||
render(chunk, important);
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
return render(chunk, important);
|
||||
}
|
||||
|
||||
std::shared_ptr<Mesh> ChunksRenderer::get(Chunk* chunk) {
|
||||
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
|
||||
if (found != meshes.end()) {
|
||||
return found->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChunksRenderer::update() {
|
||||
resultsMutex.lock();
|
||||
while (!results.empty()) {
|
||||
mesh_entry entry = results.front();
|
||||
results.pop();
|
||||
meshes[entry.key] = std::shared_ptr<Mesh>(entry.renderer.createMesh());
|
||||
inwork.erase(entry.key);
|
||||
entry.locked = false;
|
||||
entry.variable.notify_all();
|
||||
}
|
||||
resultsMutex.unlock();
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#ifndef SRC_GRAPHICS_CHUNKSRENDERER_H_
|
||||
#define SRC_GRAPHICS_CHUNKSRENDERER_H_
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <glm/glm.hpp>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "../../voxels/Block.h"
|
||||
#include "../../voxels/ChunksStorage.h"
|
||||
#include "../../settings.h"
|
||||
|
||||
class Mesh;
|
||||
class Chunk;
|
||||
class Level;
|
||||
class BlocksRenderer;
|
||||
class ContentGfxCache;
|
||||
|
||||
struct mesh_entry {
|
||||
BlocksRenderer& renderer;
|
||||
std::condition_variable& variable;
|
||||
int workerIndex;
|
||||
bool& locked;
|
||||
glm::ivec2 key;
|
||||
};
|
||||
|
||||
class ChunksRenderer {
|
||||
std::unique_ptr<BlocksRenderer> renderer;
|
||||
Level* level;
|
||||
std::unordered_map<glm::ivec2, std::shared_ptr<Mesh>> meshes;
|
||||
std::unordered_map<glm::ivec2, bool> inwork;
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
std::queue<mesh_entry> results;
|
||||
std::mutex resultsMutex;
|
||||
|
||||
std::queue<std::shared_ptr<Chunk>> jobs;
|
||||
std::condition_variable jobsMutexCondition;
|
||||
std::mutex jobsMutex;
|
||||
|
||||
bool working = true;
|
||||
const ContentGfxCache* cache;
|
||||
const EngineSettings& settings;
|
||||
std::vector<std::unique_lock<std::mutex>> workersBlocked;
|
||||
|
||||
void threadLoop(int index);
|
||||
void process(std::shared_ptr<Chunk> chunk, BlocksRenderer& renderer);
|
||||
public:
|
||||
ChunksRenderer(Level* level,
|
||||
const ContentGfxCache* cache,
|
||||
const EngineSettings& settings);
|
||||
virtual ~ChunksRenderer();
|
||||
|
||||
std::shared_ptr<Mesh> render(std::shared_ptr<Chunk> chunk, bool important);
|
||||
void unload(Chunk* chunk);
|
||||
|
||||
std::shared_ptr<Mesh> getOrRender(std::shared_ptr<Chunk> chunk, bool important);
|
||||
std::shared_ptr<Mesh> get(Chunk* chunk);
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif // SRC_GRAPHICS_CHUNKSRENDERER_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user