Core/AppFilesystemComponent: Add support for default resource parameters
This commit is contained in:
@@ -10,32 +10,50 @@
|
||||
#include <Nazara/Prerequisites.hpp>
|
||||
#include <Nazara/Core/ApplicationComponent.hpp>
|
||||
#include <Nazara/Core/Config.hpp>
|
||||
#include <Nazara/Core/ResourceParameters.hpp>
|
||||
#include <Nazara/Core/VirtualDirectory.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
class Font;
|
||||
class Image;
|
||||
class ImageStream;
|
||||
class Material;
|
||||
class MaterialInstance;
|
||||
class Mesh;
|
||||
class SoundBuffer;
|
||||
class SoundStream;
|
||||
class Texture;
|
||||
|
||||
class NAZARA_CORE_API AppFilesystemComponent : public ApplicationComponent
|
||||
{
|
||||
public:
|
||||
using ApplicationComponent::ApplicationComponent;
|
||||
inline AppFilesystemComponent(ApplicationBase& app);
|
||||
AppFilesystemComponent(const AppFilesystemComponent&) = delete;
|
||||
AppFilesystemComponent(AppFilesystemComponent&&) = delete;
|
||||
~AppFilesystemComponent() = default;
|
||||
|
||||
template<typename T, typename... Args> std::shared_ptr<T> GetOrLoad(std::string_view assetPath, Args&&... args);
|
||||
template<typename T> const typename T::Params* GetDefaultResourceParameters() const;
|
||||
|
||||
inline const VirtualDirectoryPtr& RegisterPath(std::filesystem::path filepath);
|
||||
inline const VirtualDirectoryPtr& RegisterVirtualDirectory(VirtualDirectoryPtr directory);
|
||||
template<typename T> std::shared_ptr<T> GetOrLoad(std::string_view assetPath);
|
||||
template<typename T> std::shared_ptr<T> GetOrLoad(std::string_view assetPath, typename T::Params params);
|
||||
|
||||
inline void UnregisterVirtualDirectory(const VirtualDirectoryPtr& directory);
|
||||
inline const VirtualDirectoryPtr& Mount(std::string_view name, std::filesystem::path filepath);
|
||||
inline const VirtualDirectoryPtr& Mount(std::string_view name, VirtualDirectoryPtr directory);
|
||||
|
||||
template<typename T> void SetDefaultResourceParameters(typename T::Params params);
|
||||
|
||||
AppFilesystemComponent& operator=(const AppFilesystemComponent&) = delete;
|
||||
AppFilesystemComponent& operator=(AppFilesystemComponent&&) = delete;
|
||||
|
||||
private:
|
||||
std::vector<VirtualDirectoryPtr> m_virtualDirectories;
|
||||
template<typename T> std::shared_ptr<T> GetOrLoadImpl(std::string_view assetPath, const typename T::Params& params);
|
||||
inline void RegisterResourceTypes();
|
||||
|
||||
std::vector<std::unique_ptr<ResourceParameters>> m_defaultParameters;
|
||||
VirtualDirectoryPtr m_rootDirectory;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,68 +4,153 @@
|
||||
|
||||
#include <Nazara/Core/AppFilesystemComponent.hpp>
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Core/ResourceRegistry.hpp>
|
||||
#include <stdexcept>
|
||||
#include <Nazara/Core/Debug.hpp>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
template<typename T, typename... Args>
|
||||
std::shared_ptr<T> AppFilesystemComponent::GetOrLoad(std::string_view assetPath, Args&&... args)
|
||||
namespace Detail
|
||||
{
|
||||
std::shared_ptr<T> resource;
|
||||
for (const VirtualDirectoryPtr& virtualDir : m_virtualDirectories)
|
||||
{
|
||||
auto callback = [&](const VirtualDirectory::Entry& entry)
|
||||
{
|
||||
return std::visit([&](auto&& arg)
|
||||
{
|
||||
using Param = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_base_of_v<VirtualDirectory::DirectoryEntry, Param>)
|
||||
{
|
||||
NazaraError(std::string(assetPath) + " is a directory");
|
||||
return false;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::DataPointerEntry>)
|
||||
{
|
||||
resource = T::LoadFromMemory(arg.data, arg.size, std::forward<Args>(args)...);
|
||||
return true;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::FileContentEntry>)
|
||||
{
|
||||
resource = T::LoadFromMemory(&arg.data[0], arg.data.size(), std::forward<Args>(args)...);
|
||||
return true;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::PhysicalFileEntry>)
|
||||
{
|
||||
resource = T::LoadFromFile(arg.filePath, std::forward<Args>(args)...);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
static_assert(AlwaysFalse<Param>(), "unhandled case");
|
||||
}, entry);
|
||||
};
|
||||
template<typename, typename = void>
|
||||
struct ResourceParameterHasMerge : std::false_type {};
|
||||
|
||||
if (virtualDir->GetEntry(assetPath, callback) && resource)
|
||||
return resource;
|
||||
template<typename T>
|
||||
struct ResourceParameterHasMerge<T, std::void_t<decltype(std::declval<T>().Merge(std::declval<T>()))>> : std::true_type {};
|
||||
}
|
||||
|
||||
inline AppFilesystemComponent::AppFilesystemComponent(ApplicationBase& app) :
|
||||
ApplicationComponent(app)
|
||||
{
|
||||
RegisterResourceTypes();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const typename T::Params* AppFilesystemComponent::GetDefaultResourceParameters() const
|
||||
{
|
||||
std::size_t resourceIndex = ResourceRegistry<T>::GetResourceId();
|
||||
if (resourceIndex >= m_defaultParameters.size())
|
||||
return nullptr;
|
||||
|
||||
return static_cast<const typename T::Params*>(m_defaultParameters[resourceIndex].get());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<T> AppFilesystemComponent::GetOrLoad(std::string_view assetPath)
|
||||
{
|
||||
return GetOrLoad<T>(assetPath, typename T::Params{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<T> AppFilesystemComponent::GetOrLoad(std::string_view assetPath, typename T::Params params)
|
||||
{
|
||||
if constexpr (Detail::ResourceParameterHasMerge<typename T::Params>::value)
|
||||
{
|
||||
if (const auto* defaultParams = GetDefaultResourceParameters<T>())
|
||||
params.Merge(*defaultParams);
|
||||
}
|
||||
|
||||
return GetOrLoadImpl<T>(assetPath, params);
|
||||
}
|
||||
|
||||
inline const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, std::filesystem::path filepath)
|
||||
{
|
||||
return Mount(name, std::make_shared<VirtualDirectory>(std::move(filepath)));
|
||||
}
|
||||
|
||||
inline const VirtualDirectoryPtr& AppFilesystemComponent::Mount(std::string_view name, VirtualDirectoryPtr directory)
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
m_rootDirectory = std::move(directory);
|
||||
return m_rootDirectory;
|
||||
}
|
||||
|
||||
if (!m_rootDirectory)
|
||||
m_rootDirectory = std::make_shared<VirtualDirectory>();
|
||||
|
||||
return m_rootDirectory->StoreDirectory(name, std::move(directory)).directory;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void AppFilesystemComponent::SetDefaultResourceParameters(typename T::Params params)
|
||||
{
|
||||
std::size_t resourceIndex = ResourceRegistry<T>::GetResourceId();
|
||||
if (resourceIndex >= m_defaultParameters.size())
|
||||
m_defaultParameters.resize(resourceIndex + 1);
|
||||
|
||||
m_defaultParameters[resourceIndex] = std::make_unique<typename T::Params>(std::move(params));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<T> AppFilesystemComponent::GetOrLoadImpl(std::string_view assetPath, const typename T::Params& params)
|
||||
{
|
||||
std::shared_ptr<T> resource;
|
||||
if (!m_rootDirectory)
|
||||
return resource;
|
||||
|
||||
auto callback = [&](const VirtualDirectory::Entry& entry)
|
||||
{
|
||||
return std::visit([&](auto&& arg)
|
||||
{
|
||||
using Param = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_base_of_v<VirtualDirectory::DirectoryEntry, Param>)
|
||||
{
|
||||
NazaraError(std::string(assetPath) + " is a directory");
|
||||
return false;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::DataPointerEntry>)
|
||||
{
|
||||
resource = T::LoadFromMemory(arg.data, arg.size, params);
|
||||
return true;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::FileContentEntry>)
|
||||
{
|
||||
resource = T::LoadFromMemory(&arg.data[0], arg.data.size(), params);
|
||||
return true;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Param, VirtualDirectory::PhysicalFileEntry>)
|
||||
{
|
||||
resource = T::LoadFromFile(arg.filePath, params);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
static_assert(AlwaysFalse<Param>(), "unhandled case");
|
||||
}, entry);
|
||||
};
|
||||
|
||||
m_rootDirectory->GetEntry(assetPath, callback);
|
||||
return resource;
|
||||
}
|
||||
|
||||
inline const VirtualDirectoryPtr& AppFilesystemComponent::RegisterPath(std::filesystem::path filepath)
|
||||
inline void AppFilesystemComponent::RegisterResourceTypes()
|
||||
{
|
||||
return RegisterVirtualDirectory(std::make_shared<VirtualDirectory>(std::move(filepath)));
|
||||
}
|
||||
if (ResourceRegistry<Font>::GetResourceId() != 0)
|
||||
throw std::runtime_error("Font has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
inline const VirtualDirectoryPtr& AppFilesystemComponent::RegisterVirtualDirectory(VirtualDirectoryPtr directory)
|
||||
{
|
||||
return m_virtualDirectories.emplace_back(std::move(directory));
|
||||
}
|
||||
if (ResourceRegistry<Image>::GetResourceId() != 1)
|
||||
throw std::runtime_error("Image has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
inline void AppFilesystemComponent::UnregisterVirtualDirectory(const VirtualDirectoryPtr& directory)
|
||||
{
|
||||
auto it = std::find(m_virtualDirectories.begin(), m_virtualDirectories.end(), directory);
|
||||
if (it == m_virtualDirectories.end())
|
||||
m_virtualDirectories.erase(it);
|
||||
if (ResourceRegistry<ImageStream>::GetResourceId() != 2)
|
||||
throw std::runtime_error("ImageStream has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<Material>::GetResourceId() != 3)
|
||||
throw std::runtime_error("Material has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<MaterialInstance>::GetResourceId() != 4)
|
||||
throw std::runtime_error("MaterialInstance has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<Mesh>::GetResourceId() != 5)
|
||||
throw std::runtime_error("Mesh has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<SoundBuffer>::GetResourceId() != 6)
|
||||
throw std::runtime_error("SoundBuffer has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<SoundStream>::GetResourceId() != 7)
|
||||
throw std::runtime_error("SoundStream has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
|
||||
if (ResourceRegistry<Texture>::GetResourceId() != 8)
|
||||
throw std::runtime_error("Texture has wrong resource index, please initialize AppFilesystemComponent before using ResourceRegistry");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Nz
|
||||
Application(Application&&) = delete;
|
||||
~Application();
|
||||
|
||||
template<typename T, typename... Args> T& AddComponent(Args&&... args);
|
||||
|
||||
Application& operator=(const Application&) = delete;
|
||||
Application& operator=(Application&&) = delete;
|
||||
|
||||
|
||||
@@ -7,6 +7,39 @@
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
template<typename, typename, typename = void>
|
||||
struct ModuleHasRegister : std::false_type {};
|
||||
|
||||
template<typename M, typename C>
|
||||
struct ModuleHasRegister<M, C, std::void_t<decltype(std::declval<M>().RegisterComponent(std::declval<C&>()))>> : std::true_type {};
|
||||
|
||||
template<typename> struct ModuleRegisterer;
|
||||
|
||||
template<typename Module, typename... Rest>
|
||||
struct ModuleRegisterer<TypeList<Module, Rest...>>
|
||||
{
|
||||
template<typename T, typename C>
|
||||
static void Register(T& modules, C& component)
|
||||
{
|
||||
if constexpr (ModuleHasRegister<Module, C>::value)
|
||||
modules.template Get<Module>().RegisterComponent(component);
|
||||
|
||||
ModuleRegisterer<TypeList<Rest...>>::Register(modules, component);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ModuleRegisterer<TypeList<>>
|
||||
{
|
||||
template<typename T, typename C>
|
||||
static void Register(T& /*modules*/, C& /*component*/)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename... ModuleList>
|
||||
template<typename... ModuleConfig>
|
||||
Application<ModuleList...>::Application(ModuleConfig&&... configs) :
|
||||
@@ -30,6 +63,16 @@ namespace Nz
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... ModuleList>
|
||||
template<typename T, typename... Args>
|
||||
T& Application<ModuleList...>::AddComponent(Args&&... args)
|
||||
{
|
||||
T& component = ApplicationBase::AddComponent<T>(std::forward<Args>(args)...);
|
||||
Detail::ModuleRegisterer<typename decltype(m_modules)::ModuleTypeList>::template Register(m_modules, component);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
template<typename... ModuleList>
|
||||
Application<ModuleList...>::~Application()
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Nz
|
||||
ApplicationBase(ApplicationBase&&) = delete;
|
||||
~ApplicationBase() = default;
|
||||
|
||||
template<typename T, typename... Args> T& AddComponent(Args&&... args);
|
||||
template<typename F> void AddUpdater(F&& functor);
|
||||
|
||||
inline void ClearComponents();
|
||||
@@ -43,6 +42,9 @@ namespace Nz
|
||||
ApplicationBase& operator=(const ApplicationBase&) = delete;
|
||||
ApplicationBase& operator=(ApplicationBase&&) = delete;
|
||||
|
||||
protected:
|
||||
template<typename T, typename... Args> T& AddComponent(Args&&... args);
|
||||
|
||||
private:
|
||||
std::atomic_bool m_running;
|
||||
std::vector<std::unique_ptr<ApplicationComponent>> m_components;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Nz
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
std::size_t ComponentCounter()
|
||||
inline std::size_t ComponentCounter()
|
||||
{
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
|
||||
@@ -13,14 +13,13 @@ namespace Nz
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
template<typename>
|
||||
struct BuildDepList;
|
||||
|
||||
template<typename Module, typename... Modules>
|
||||
struct ModuleTuple : ModuleTuple<Module>, ModuleTuple<Modules...>
|
||||
{
|
||||
template<typename... ModuleConfig>
|
||||
ModuleTuple(ModuleConfig&&... configs);
|
||||
|
||||
template<typename T> T& Get();
|
||||
};
|
||||
|
||||
template<typename Module>
|
||||
@@ -29,10 +28,16 @@ namespace Nz
|
||||
template<typename... ModuleConfig>
|
||||
ModuleTuple(ModuleConfig&&... configs);
|
||||
|
||||
template<typename T> T& Get();
|
||||
|
||||
Module m;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename> struct OrderedModuleDependencyList;
|
||||
|
||||
template<typename ModuleList> using OrderedModuleDependencies = TypeListUnique<typename OrderedModuleDependencyList<ModuleList>::Result>;
|
||||
|
||||
template<typename... ModuleList>
|
||||
class Modules
|
||||
{
|
||||
@@ -41,12 +46,31 @@ namespace Nz
|
||||
Modules(ModuleConfig&&... configs);
|
||||
~Modules() = default;
|
||||
|
||||
template<typename T> T& Get();
|
||||
|
||||
using ModuleTypeList = OrderedModuleDependencies<TypeList<ModuleList...>>;
|
||||
|
||||
private:
|
||||
using OrderedModuleList = TypeListUnique<typename Detail::BuildDepList<TypeList<ModuleList...>>::Result>;
|
||||
using Tuple = TypeListInstantiate<OrderedModuleList, Detail::ModuleTuple>;
|
||||
using Tuple = TypeListInstantiate<ModuleTypeList, Detail::ModuleTuple>;
|
||||
|
||||
Tuple m_modules;
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct OrderedModuleDependencyList<TypeList<>>
|
||||
{
|
||||
using Result = TypeList<>;
|
||||
};
|
||||
|
||||
template<typename Module, typename... ModuleList>
|
||||
struct OrderedModuleDependencyList<TypeList<Module, ModuleList...>>
|
||||
{
|
||||
using ModuleDependencies = typename OrderedModuleDependencyList<typename Module::Dependencies>::Result;
|
||||
using ModuleDependenciesIncModule = TypeListAppend<ModuleDependencies, Module>;
|
||||
using RestDependencies = typename OrderedModuleDependencyList<TypeList<ModuleList...>>::Result;
|
||||
using Result = TypeListConcat<ModuleDependenciesIncModule, RestDependencies>;
|
||||
};
|
||||
}
|
||||
|
||||
#include <Nazara/Core/Modules.inl>
|
||||
|
||||
@@ -39,6 +39,16 @@ namespace Nz
|
||||
{
|
||||
}
|
||||
|
||||
template<typename Module, typename... Modules>
|
||||
template<typename T>
|
||||
T& ModuleTuple<Module, Modules...>::Get()
|
||||
{
|
||||
if constexpr (std::is_same_v<T, Module>)
|
||||
return ModuleTuple<Module>::template Get<T>();
|
||||
else
|
||||
return ModuleTuple<Modules...>::template Get<T>();
|
||||
}
|
||||
|
||||
template<typename Module>
|
||||
template<typename... ModuleConfig>
|
||||
ModuleTuple<Module>::ModuleTuple(ModuleConfig&&... configs) :
|
||||
@@ -46,20 +56,13 @@ namespace Nz
|
||||
{
|
||||
}
|
||||
|
||||
template<>
|
||||
struct BuildDepList<TypeList<>>
|
||||
template<typename Module>
|
||||
template<typename T>
|
||||
T& ModuleTuple<Module>::Get()
|
||||
{
|
||||
using Result = TypeList<>;
|
||||
};
|
||||
|
||||
template<typename Module, typename... ModuleList>
|
||||
struct BuildDepList<TypeList<Module, ModuleList...>>
|
||||
{
|
||||
using ModuleDependencies = typename BuildDepList<typename Module::Dependencies>::Result;
|
||||
using ModuleDependenciesIncModule = TypeListAppend<ModuleDependencies, Module>;
|
||||
using RestDependencies = typename BuildDepList<TypeList<ModuleList...>>::Result;
|
||||
using Result = TypeListConcat<ModuleDependenciesIncModule, RestDependencies>;
|
||||
};
|
||||
static_assert(std::is_same_v<T, Module>, "module is not in the list");
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... ModuleList>
|
||||
@@ -68,6 +71,13 @@ namespace Nz
|
||||
m_modules(std::forward<ModuleConfig>(configs)...)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... ModuleList>
|
||||
template<typename T>
|
||||
T& Modules<ModuleList...>::Get()
|
||||
{
|
||||
return m_modules.template Get<T>();
|
||||
}
|
||||
}
|
||||
|
||||
#include <Nazara/Core/DebugOff.hpp>
|
||||
|
||||
24
include/Nazara/Core/ResourceRegistry.hpp
Normal file
24
include/Nazara/Core/ResourceRegistry.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
|
||||
// This file is part of the "Nazara Engine - Core module"
|
||||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef NAZARA_CORE_RESOURCEREGISTRY_HPP
|
||||
#define NAZARA_CORE_RESOURCEREGISTRY_HPP
|
||||
|
||||
#include <Nazara/Prerequisites.hpp>
|
||||
#include <Nazara/Core/Config.hpp>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
template<typename T>
|
||||
struct ResourceRegistry
|
||||
{
|
||||
static std::size_t GetResourceId();
|
||||
};
|
||||
}
|
||||
|
||||
#include <Nazara/Core/ResourceRegistry.inl>
|
||||
|
||||
#endif // NAZARA_CORE_RESOURCEREGISTRY_HPP
|
||||
27
include/Nazara/Core/ResourceRegistry.inl
Normal file
27
include/Nazara/Core/ResourceRegistry.inl
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
|
||||
// This file is part of the "Nazara Engine - Core module"
|
||||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Core/ResourceRegistry.hpp>
|
||||
#include <Nazara/Core/Debug.hpp>
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
inline std::size_t ResourceTypeIndexCounter()
|
||||
{
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::size_t ResourceRegistry<T>::GetResourceId()
|
||||
{
|
||||
static std::size_t typeId = Detail::ResourceTypeIndexCounter();
|
||||
return typeId;
|
||||
}
|
||||
}
|
||||
|
||||
#include <Nazara/Core/DebugOff.hpp>
|
||||
Reference in New Issue
Block a user