support module system
This commit is contained in:
parent
b17f3ef1d3
commit
7e2712848c
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "emmylua_new",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "EmmyLua New Debug",
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 9966,
|
||||||
|
"ext": [
|
||||||
|
".lua",
|
||||||
|
".lua.txt",
|
||||||
|
".lua.bytes"
|
||||||
|
],
|
||||||
|
"ideConnectDebugger": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2
engine/3rdparty/xmake.lua
vendored
2
engine/3rdparty/xmake.lua
vendored
@ -1 +1 @@
|
|||||||
add_requires("spdlog")
|
add_requires("spdlog", "lemon")
|
||||||
@ -27,6 +27,17 @@ namespace gen {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
template<typename T>
|
||||||
|
struct JsonSerde<T, std::enable_if_t<refl::is_container_v<T>>> {
|
||||||
|
inline static bool Read(yyjson_val* val, const void* ptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
inline static yyjson_mut_val* Write(yyjson_mut_doc* doc, const void* ptr) {
|
||||||
|
T& v = *(T*)ptr;
|
||||||
|
yyjson_mut_val* obj = yyjson_mut_obj(doc);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
namespace api {
|
namespace api {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#include "serde.h"
|
#include "serde.h"
|
||||||
namespace api {
|
namespace api {
|
||||||
// 定义 yyjson_alc 适配器类
|
// 定义 yyjson_alc 适配器类
|
||||||
inline yyjson_alc JsonAllocatorAdapter(std::pmr::memory_resource* mr = &FramePool) {
|
inline yyjson_alc JsonAllocatorAdapter(std::pmr::memory_resource* mr = FramePool) {
|
||||||
// 初始化 yyjson_alc 结构体
|
// 初始化 yyjson_alc 结构体
|
||||||
yyjson_alc alc;
|
yyjson_alc alc;
|
||||||
alc.malloc = [](void* ctx, size_t size) -> void* {
|
alc.malloc = [](void* ctx, size_t size) -> void* {
|
||||||
@ -29,7 +29,7 @@ namespace api {
|
|||||||
}
|
}
|
||||||
inline JsonFuncTable JsonArchive::BuildFuncTable()
|
inline JsonFuncTable JsonArchive::BuildFuncTable()
|
||||||
{
|
{
|
||||||
JsonFuncTable funcTable{ &MemPool };
|
JsonFuncTable funcTable{ MemPool };
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
#define RegisterAny(T) funcTable.emplace(&TypeInfo<T>::StaticClass,JsonVTable::Make<T>())
|
#define RegisterAny(T) funcTable.emplace(&TypeInfo<T>::StaticClass,JsonVTable::Make<T>())
|
||||||
#include "../register.inl"
|
#include "../register.inl"
|
||||||
|
|||||||
@ -9,6 +9,14 @@ namespace api {
|
|||||||
Reload = 1,
|
Reload = 1,
|
||||||
};
|
};
|
||||||
ENABLE_BITMASK_OPERATORS(EModuleFlag);
|
ENABLE_BITMASK_OPERATORS(EModuleFlag);
|
||||||
|
struct ModuleDependency {
|
||||||
|
UPROPERTY()
|
||||||
|
Name name;
|
||||||
|
UPROPERTY()
|
||||||
|
Name version;
|
||||||
|
UPROPERTY()
|
||||||
|
Name kind;
|
||||||
|
};
|
||||||
struct ModuleInfo {
|
struct ModuleInfo {
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
EModuleFlag flag{0};
|
EModuleFlag flag{0};
|
||||||
@ -28,6 +36,9 @@ namespace api {
|
|||||||
Name url; //!< url of the plugin
|
Name url; //!< url of the plugin
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
Name copyright; //!< copyright of the plugin
|
Name copyright; //!< copyright of the plugin
|
||||||
|
UPROPERTY()
|
||||||
|
pmr::vector<ModuleDependency> dependencies;
|
||||||
|
ModuleInfo(pmr::memory_resource* mr = MemPool) : dependencies(mr){};
|
||||||
public:
|
public:
|
||||||
bool IsReload() {
|
bool IsReload() {
|
||||||
return !!(flag & EModuleFlag::Reload);
|
return !!(flag & EModuleFlag::Reload);
|
||||||
@ -43,8 +54,8 @@ namespace api {
|
|||||||
virtual void EndLoad() {}
|
virtual void EndLoad() {}
|
||||||
};
|
};
|
||||||
struct IModule {
|
struct IModule {
|
||||||
friend class ModuleManagerImpl;
|
|
||||||
public:
|
public:
|
||||||
|
friend class ModuleManager;
|
||||||
using CreatePFN = IModule * (*)();
|
using CreatePFN = IModule * (*)();
|
||||||
IModule() = default;
|
IModule() = default;
|
||||||
IModule(const IModule& rhs) = delete;
|
IModule(const IModule& rhs) = delete;
|
||||||
@ -53,7 +64,7 @@ namespace api {
|
|||||||
|
|
||||||
virtual void OnLoad(int argc, char** argv) = 0;
|
virtual void OnLoad(int argc, char** argv) = 0;
|
||||||
virtual void OnUnload() = 0;
|
virtual void OnUnload() = 0;
|
||||||
virtual const char* MetaData(void) = 0;
|
virtual void InitMetaData(void) = 0;
|
||||||
|
|
||||||
virtual int Main(int argc, char** argv) { return 0; }
|
virtual int Main(int argc, char** argv) { return 0; }
|
||||||
virtual const ModuleInfo* GetModuleInfo()
|
virtual const ModuleInfo* GetModuleInfo()
|
||||||
@ -62,7 +73,7 @@ namespace api {
|
|||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
ModuleInfo mInfo;
|
ModuleInfo mInfo;
|
||||||
std::vector<IModuleSubsystem*> mSubSystems;
|
pmr::vector<IModuleSubsystem*> mSubSystems;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#include "module.inl"
|
#include "module.inl"
|
||||||
|
|||||||
@ -5,8 +5,13 @@ namespace api {
|
|||||||
}
|
}
|
||||||
SharedLibrary mSharedLib;
|
SharedLibrary mSharedLib;
|
||||||
};
|
};
|
||||||
struct IStaticModule : public IModule {
|
struct DefaultDynamicModule : public IDynamicModule {
|
||||||
|
DefaultDynamicModule(Name name) : IDynamicModule() {};
|
||||||
|
void OnLoad(int argc, char** argv) override {};
|
||||||
|
void OnUnload() override {};
|
||||||
|
void InitMetaData(void) override {};
|
||||||
};
|
};
|
||||||
|
using IStaticModule = IModule;
|
||||||
struct IHotfixModule : public IDynamicModule {
|
struct IHotfixModule : public IDynamicModule {
|
||||||
void* state = nullptr;
|
void* state = nullptr;
|
||||||
//IHotfixModule(){ mInfo.flag |= EModuleFlag::Reload; }
|
//IHotfixModule(){ mInfo.flag |= EModuleFlag::Reload; }
|
||||||
@ -15,11 +20,11 @@ namespace api {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
#define IMPLEMENT_STATIC_MODULE(ModuleImplClass, ModuleName) \
|
#define IMPLEMENT_STATIC_MODULE(ModuleImplClass, ModuleName) \
|
||||||
inline static const api::ModuleRegistrantImpl<ModuleImplClass> __RegisterModule##ModuleName((const char*)#ModuleName);
|
inline const api::ModuleRegistrantImpl<ModuleImplClass> __RegisterModule__##ModuleName(#ModuleName);
|
||||||
|
|
||||||
#define IMPLEMENT_DYNAMIC_MODULE(ModuleImplClass, ModuleName) \
|
#define IMPLEMENT_DYNAMIC_MODULE(DLL_APL, ModuleImplClass, ModuleName) \
|
||||||
extern "C" api::IModule* __newDynamicModule##ModuleName() \
|
using __##ModuleName##__module = ModuleImplClass; \
|
||||||
{ \
|
extern "C" DLL_APL api::IModule* __newDynamicModule__##ModuleName() \
|
||||||
return new ModuleImplClass(); \
|
{ \
|
||||||
}
|
return new(MemPool) ModuleImplClass(); \
|
||||||
#define MODULE_DEPENDENCY(...)
|
}
|
||||||
@ -1,31 +1,54 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
namespace api {
|
namespace api {
|
||||||
template<typename T1, typename T2, typename Hasher = std::hash<T1>>
|
|
||||||
using table = std::pmr::unordered_map<T1, T2, Hasher>;
|
|
||||||
class ModuleManager
|
class ModuleManager
|
||||||
{
|
{
|
||||||
friend struct IModule;
|
friend struct IModule;
|
||||||
private:
|
private:
|
||||||
|
struct ModuleBlock {
|
||||||
|
bool isActive;
|
||||||
|
};
|
||||||
|
struct ExecuteInfo {
|
||||||
|
Name name;
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
bool isActive;
|
||||||
|
};
|
||||||
|
ExecuteInfo mInfo;
|
||||||
SharedLibrary mProcessLib;
|
SharedLibrary mProcessLib;
|
||||||
table<Name, IModule*> mModuleTable;
|
pmr::table<Name, ModuleBlock> mModuleBlocks{ GetMemPool() };
|
||||||
|
pmr::table<Name, IModule*> mModuleTable{GetMemPool()};
|
||||||
|
pmr::table<Name, IModule::CreatePFN> mInitializeTable{ GetMemPool() };
|
||||||
public:
|
public:
|
||||||
ModuleManager() = default;
|
|
||||||
IModule* GetModule(Name name);
|
|
||||||
bool RegisterModule(Name name, IModule::CreatePFN fn);
|
|
||||||
static ModuleManager* Ptr();
|
static ModuleManager* Ptr();
|
||||||
|
public:
|
||||||
|
ModuleManager();
|
||||||
|
~ModuleManager();
|
||||||
|
IModule* GetModule(Name name);
|
||||||
|
void RegisterModule(Name name, IModule::CreatePFN fn) { mInitializeTable[name] = fn; }
|
||||||
|
void CreateModule(Name name, bool shared);
|
||||||
|
void DestroyModule(Name name);
|
||||||
|
void MakeGraph(Name name, bool shared, int argc, char** argv);
|
||||||
|
void DestroyGraph();
|
||||||
|
protected:
|
||||||
|
IModule* spawnDynamicModule(Name name, bool hotfix);
|
||||||
|
IModule* spawnStaticModule(Name name);
|
||||||
};
|
};
|
||||||
struct empty {
|
|
||||||
|
|
||||||
};
|
|
||||||
static empty e;
|
|
||||||
template <typename ModuleClass>
|
template <typename ModuleClass>
|
||||||
struct ModuleRegistrantImpl {
|
struct ModuleRegistrantImpl {
|
||||||
ModuleRegistrantImpl(const char* InModuleName)
|
ModuleRegistrantImpl(const char* InModuleName)
|
||||||
{
|
{
|
||||||
ModuleManager::Ptr()->RegisterModule(InModuleName, []() {
|
ModuleManager::Ptr()->RegisterModule(InModuleName, []()->IModule* {
|
||||||
return new ModuleClass();
|
return new ModuleClass();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
class CoreModule : public IStaticModule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void OnLoad(int argc, char** argv) override;
|
||||||
|
void OnUnload() override;
|
||||||
|
void InitMetaData(void) override;
|
||||||
|
};
|
||||||
|
IMPLEMENT_STATIC_MODULE(CoreModule, core)
|
||||||
}
|
}
|
||||||
69
engine/modules/engine/core/include/os/file_manager.h
Normal file
69
engine/modules/engine/core/include/os/file_manager.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "singleton.h"
|
||||||
|
#include "package_path.h"
|
||||||
|
namespace api
|
||||||
|
{
|
||||||
|
class FileManager : public Singleton<FileManager>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FileManager();
|
||||||
|
~FileManager();
|
||||||
|
public:
|
||||||
|
void Mount(Name name, const string& path) {
|
||||||
|
MountMap.emplace(name, std::make_pair(name, path));
|
||||||
|
}
|
||||||
|
std::pair<string, string> FindMount(Name id) {
|
||||||
|
auto it = MountMap.find(id);
|
||||||
|
if (it != MountMap.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
string_view FindMountName(Name id) {
|
||||||
|
auto pair = FindMount(id);
|
||||||
|
return pair.first;
|
||||||
|
}
|
||||||
|
string_view FindMountPath(Name id) {
|
||||||
|
auto pair = FindMount(id);
|
||||||
|
return pair.second;
|
||||||
|
}
|
||||||
|
string_view FindPathView(Name name) {
|
||||||
|
auto it = FileMap.find(name);
|
||||||
|
if (it != FileMap.end()) {
|
||||||
|
return it->second.path;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
string_view FindOrPathView(Name name) {
|
||||||
|
auto it = FileMap.find(name);
|
||||||
|
if (it != FileMap.end()) {
|
||||||
|
return it->second.path;
|
||||||
|
}
|
||||||
|
auto res = FileMap.emplace(name, FileBlock{ FileFlag::File_Default, name.ToString()});
|
||||||
|
return res.first->second.path;
|
||||||
|
}
|
||||||
|
uint32_t FindPathFlag(const PackagePath& pack_path) {
|
||||||
|
auto it = FileMap.find(pack_path());
|
||||||
|
if (it == FileMap.end()) {
|
||||||
|
return FileFlag::File_Not_Exist;
|
||||||
|
}
|
||||||
|
return it->second.flag;
|
||||||
|
}
|
||||||
|
FileBlock* FindPathBlock(const PackagePath& pack_path) {
|
||||||
|
auto it = FileMap.find(pack_path());
|
||||||
|
if (it == FileMap.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
void SaveMountMap();
|
||||||
|
void LoadFileMap();
|
||||||
|
void SaveFileMap();
|
||||||
|
private:
|
||||||
|
pmr::table<Name, std::pair<string, string>> MountMap{GetMemPool()};
|
||||||
|
pmr::table<Name, FileBlock> FileMap{GetMemPool()};
|
||||||
|
public:
|
||||||
|
//外界不应该使用绝对路径
|
||||||
|
pmr::string RealPath(const PackagePath& pack_path);
|
||||||
|
};
|
||||||
|
}
|
||||||
72
engine/modules/engine/core/include/os/package_path.h
Normal file
72
engine/modules/engine/core/include/os/package_path.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include "pmr/name.h"
|
||||||
|
namespace api
|
||||||
|
{
|
||||||
|
using std::string;
|
||||||
|
using std::string_view;
|
||||||
|
using pmr::Name;
|
||||||
|
enum FileFlag : uint32_t {
|
||||||
|
File_Default = 0,
|
||||||
|
File_Success = 1 << 0,
|
||||||
|
File_Binary = 1 << 1,
|
||||||
|
File_Compress = 1 << 2,
|
||||||
|
File_Http = 1 << 3,
|
||||||
|
File_Not_Exist = 1 << 4,
|
||||||
|
File_Error = 1 << 5,
|
||||||
|
};
|
||||||
|
//占位符浪费了四个字节
|
||||||
|
struct FileBlock {
|
||||||
|
uint32_t flag{ 0 };
|
||||||
|
string path;
|
||||||
|
string addr;
|
||||||
|
operator bool() {
|
||||||
|
return !(flag & FileFlag::File_Not_Exist);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct PackagePath {
|
||||||
|
string_view path;
|
||||||
|
PackagePath() {};
|
||||||
|
PackagePath(const char* path) : path(path) {}
|
||||||
|
PackagePath(string_view path) : path(path) {}
|
||||||
|
PackagePath(const string& path) : path(path) {}
|
||||||
|
PackagePath(const pmr::string& path) : path(path) {}
|
||||||
|
operator bool() {
|
||||||
|
return path != "";
|
||||||
|
}
|
||||||
|
string_view operator()() const {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
size_t size() const {
|
||||||
|
return path.size();
|
||||||
|
}
|
||||||
|
pmr::string operator+(const char* suffix) const {
|
||||||
|
return pmr::string(path, FramePool) + suffix;
|
||||||
|
}
|
||||||
|
string_view ParsePackage()const {
|
||||||
|
string_view name;
|
||||||
|
if (path[0] == '/') {
|
||||||
|
size_t pos = path.find('/', 1);
|
||||||
|
if (pos != string::npos) {
|
||||||
|
return path.substr(1, pos - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
string_view GetFileName()const {
|
||||||
|
size_t pos = path.rfind('/');
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
return path.substr(pos + 1);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Name GetExtension() const {
|
||||||
|
size_t pos = path.rfind('.');
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
return path.substr(pos);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
PackagePath ToSuffixPath(string_view suffix)const;
|
||||||
|
PackagePath SafePath()const;
|
||||||
|
pmr::string RealPath()const;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,9 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "os/package_path.h"
|
||||||
namespace api {
|
namespace api {
|
||||||
using NativeLibHandle = void*;
|
using NativeLibHandle = void*;
|
||||||
class SharedLibrary {
|
class SharedLibrary {
|
||||||
NativeLibHandle mHandle;
|
NativeLibHandle mHandle;
|
||||||
public:
|
public:
|
||||||
bool Load(const char* path = nullptr);
|
bool Load(PackagePath path = "");
|
||||||
|
void* GetSymbol(const char* name);
|
||||||
|
|
||||||
|
static string_view GetExtensionName();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
#include "module/module_manager.h"
|
||||||
|
#include "os/file_manager.h"
|
||||||
|
namespace api {
|
||||||
|
void CoreModule::OnLoad(int argc, char** argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void CoreModule::OnUnload()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void CoreModule::InitMetaData(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,103 @@
|
|||||||
#include "module/module_manager.h"
|
#include "module/module_manager.h"
|
||||||
|
#include "os/file_manager.h"
|
||||||
namespace api {
|
namespace api {
|
||||||
ModuleManager* ModuleManager::Ptr()
|
ModuleManager* ModuleManager::Ptr()
|
||||||
{
|
{
|
||||||
static ModuleManager* ptr;
|
static ModuleManager ptr;
|
||||||
if (ptr) {
|
return &ptr;
|
||||||
return ptr;
|
}
|
||||||
|
ModuleManager::ModuleManager()
|
||||||
|
{
|
||||||
|
GetMemPool();
|
||||||
|
GetFramePool();
|
||||||
|
mProcessLib.Load();
|
||||||
|
new FileManager();
|
||||||
|
}
|
||||||
|
ModuleManager::~ModuleManager()
|
||||||
|
{
|
||||||
|
DestroyGraph();
|
||||||
|
delete FileManager::Ptr();
|
||||||
|
delete GetFramePool();
|
||||||
|
delete GetMemPool();
|
||||||
|
}
|
||||||
|
void ModuleManager::CreateModule(Name name, bool shared)
|
||||||
|
{
|
||||||
|
bool hotfix = true;
|
||||||
|
IModule* module = shared ?
|
||||||
|
spawnDynamicModule(name, hotfix) :
|
||||||
|
spawnStaticModule(name);
|
||||||
|
if (!module) { return; }
|
||||||
|
mModuleBlocks[name] = ModuleBlock{false};
|
||||||
|
ModuleBlock& block = mModuleBlocks[name];
|
||||||
|
auto& moduleInfo = module->mInfo;
|
||||||
|
for (auto& dep : moduleInfo.dependencies) {
|
||||||
|
if(auto it = mModuleBlocks.find(name); it != mModuleBlocks.end())
|
||||||
|
CreateModule(dep.name, dep.kind == pmr::FName("shared"));
|
||||||
}
|
}
|
||||||
ptr = new ModuleManager();
|
module->OnLoad(mInfo.argc, mInfo.argv);
|
||||||
ptr->mProcessLib.Load();
|
block.isActive = true;
|
||||||
return ptr;
|
}
|
||||||
|
void ModuleManager::DestroyModule(Name name)
|
||||||
|
{
|
||||||
|
auto it = mModuleBlocks.find(name);
|
||||||
|
if (it == mModuleBlocks.end() || !it->second.isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IModule* module = mModuleTable[name];
|
||||||
|
auto& moduleInfo = module->mInfo;
|
||||||
|
for (auto& dep : moduleInfo.dependencies) {
|
||||||
|
DestroyModule(dep.name);
|
||||||
|
}
|
||||||
|
it->second.isActive = false;
|
||||||
|
}
|
||||||
|
void ModuleManager::MakeGraph(Name name, bool shared, int argc, char** argv)
|
||||||
|
{
|
||||||
|
mInfo = { name, argc, argv , true};
|
||||||
|
CreateModule(name, shared);
|
||||||
|
}
|
||||||
|
void ModuleManager::DestroyGraph()
|
||||||
|
{
|
||||||
|
if(mInfo.isActive)
|
||||||
|
DestroyModule(mInfo.name);
|
||||||
|
}
|
||||||
|
IModule* ModuleManager::spawnDynamicModule(Name name, bool hotfix)
|
||||||
|
{
|
||||||
|
if (auto it = mModuleTable.find(name); it != mModuleTable.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
SharedLibrary sharedLib;
|
||||||
|
string_view name_view = name.ToStringView();
|
||||||
|
pmr::string newFuncName("__newDynamicModule__", FramePool);
|
||||||
|
newFuncName.append(name_view);
|
||||||
|
void* newFuncAddr = mProcessLib.GetSymbol(newFuncName.data());
|
||||||
|
if (!newFuncAddr) {
|
||||||
|
pmr::string libPath("/exe/", FramePool);
|
||||||
|
libPath.reserve(10 + name_view.size());
|
||||||
|
libPath.append(name_view);
|
||||||
|
libPath.append(SharedLibrary::GetExtensionName());
|
||||||
|
if (sharedLib.Load(libPath)) {
|
||||||
|
newFuncAddr = sharedLib.GetSymbol(newFuncName.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IDynamicModule* module = newFuncAddr ? (IDynamicModule*)((IModule::CreatePFN)newFuncAddr)() : new(MemPool) DefaultDynamicModule(name);
|
||||||
|
mModuleTable[name] = module;
|
||||||
|
module->mSharedLib = sharedLib;
|
||||||
|
module->InitMetaData();
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
IModule* ModuleManager::spawnStaticModule(Name name)
|
||||||
|
{
|
||||||
|
if (auto it = mModuleTable.find(name); it != mModuleTable.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
auto it = mInitializeTable.find(name);
|
||||||
|
if (it == mInitializeTable.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
IModule* module = it->second();
|
||||||
|
module->InitMetaData();
|
||||||
|
mModuleTable[name] = module;
|
||||||
|
return module;
|
||||||
}
|
}
|
||||||
IModule* ModuleManager::GetModule(Name name)
|
IModule* ModuleManager::GetModule(Name name)
|
||||||
{
|
{
|
||||||
@ -18,8 +107,4 @@ namespace api {
|
|||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
bool ModuleManager::RegisterModule(Name name, IModule::CreatePFN fn)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
43
engine/modules/engine/core/src/os/file_manager.cpp
Normal file
43
engine/modules/engine/core/src/os/file_manager.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "os/file_manager.h"
|
||||||
|
#include "os/file_system.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include "zlog.h"
|
||||||
|
namespace api {
|
||||||
|
FileManager::FileManager()
|
||||||
|
{
|
||||||
|
Mount("exe", fs::GetExecutablePath());
|
||||||
|
Mount("engine", fs::GetWorkPath());
|
||||||
|
LoadFileMap();
|
||||||
|
}
|
||||||
|
FileManager::~FileManager()
|
||||||
|
{
|
||||||
|
SaveMountMap();
|
||||||
|
SaveFileMap();
|
||||||
|
}
|
||||||
|
void FileManager::SaveMountMap()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void FileManager::LoadFileMap()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
void FileManager::SaveFileMap()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
pmr::string FileManager::RealPath(const PackagePath& pack_path)
|
||||||
|
{
|
||||||
|
string_view name = pack_path.ParsePackage();
|
||||||
|
string_view pre_path = FindMountPath(name);
|
||||||
|
if (name.empty() || pre_path.empty()) {
|
||||||
|
return pmr::string(pack_path(), FramePool);
|
||||||
|
}
|
||||||
|
pmr::string path{FramePool};
|
||||||
|
path.reserve(pre_path.size() + pack_path.size() - name.size() - 1);
|
||||||
|
path.append(pre_path);
|
||||||
|
path.append(pack_path().substr(name.size() + 1));
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
25
engine/modules/engine/core/src/os/package_path.cpp
Normal file
25
engine/modules/engine/core/src/os/package_path.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "os/package_path.h"
|
||||||
|
#include "os/file_manager.h"
|
||||||
|
namespace api {
|
||||||
|
PackagePath PackagePath::ToSuffixPath(string_view suffix)const
|
||||||
|
{
|
||||||
|
string_view name = ParsePackage();
|
||||||
|
if (name.empty() || suffix.empty())
|
||||||
|
return *this;
|
||||||
|
string suffixPath;
|
||||||
|
suffixPath.reserve(path.size() + suffix.size());
|
||||||
|
suffixPath.append(name);
|
||||||
|
suffixPath.append(suffix);
|
||||||
|
suffixPath.append(path.substr(name.size()));
|
||||||
|
return PackagePath{ FileManager::Ptr()->FindPathView(suffixPath) };
|
||||||
|
}
|
||||||
|
PackagePath PackagePath::SafePath()const
|
||||||
|
{
|
||||||
|
return PackagePath{ FileManager::Ptr()->FindPathView(path) };
|
||||||
|
}
|
||||||
|
pmr::string PackagePath::RealPath() const
|
||||||
|
{
|
||||||
|
return FileManager::Ptr()->RealPath(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,21 +1,35 @@
|
|||||||
#include "os/shared_library.h"
|
#include "os/shared_library.h"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
namespace api {
|
namespace api {
|
||||||
bool SharedLibrary::Load(const char* path)
|
bool SharedLibrary::Load(PackagePath path)
|
||||||
{
|
{
|
||||||
if (path == nullptr)
|
if (path)
|
||||||
{
|
{
|
||||||
mHandle = GetModuleHandle(nullptr);
|
auto rpath = path.RealPath();
|
||||||
|
mHandle = GetModuleHandle(rpath.c_str());
|
||||||
|
if (mHandle == NULL)
|
||||||
|
{
|
||||||
|
mHandle = LoadLibrary(rpath.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mHandle = GetModuleHandle(path);
|
mHandle = GetModuleHandle(nullptr);
|
||||||
if (mHandle == NULL)
|
|
||||||
{
|
|
||||||
mHandle = LoadLibrary(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return mHandle;
|
return mHandle;
|
||||||
}
|
}
|
||||||
|
void* SharedLibrary::GetSymbol(const char* name)
|
||||||
|
{
|
||||||
|
void* addr = (void*)GetProcAddress((HMODULE)mHandle, name);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
string_view SharedLibrary::GetExtensionName()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return string_view(".dll");
|
||||||
|
#elif __linux__
|
||||||
|
return string_view(".so");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,5 +6,4 @@ static_component("core","engine")
|
|||||||
add_headerfiles("include/**.h","include/**.inl")
|
add_headerfiles("include/**.h","include/**.inl")
|
||||||
add_files("src/**.cpp")
|
add_files("src/**.cpp")
|
||||||
add_deps("zlib", {public = true})
|
add_deps("zlib", {public = true})
|
||||||
add_packages("spdlog", {public = true})
|
add_packages("spdlog", {public = true})
|
||||||
--add_syslinks("Kernel32")
|
|
||||||
@ -2,7 +2,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
namespace pmr {
|
namespace pmr {
|
||||||
class FrameAllocator : public std::pmr::memory_resource {
|
using std::pmr::memory_resource;
|
||||||
|
using std::pmr::vector;
|
||||||
|
class FrameAllocator : public memory_resource {
|
||||||
private:
|
private:
|
||||||
char* buffer;
|
char* buffer;
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
@ -23,9 +25,9 @@ namespace pmr {
|
|||||||
void move_clear() { buffer = nullptr; capacity = 0; offset = 0; };
|
void move_clear() { buffer = nullptr; capacity = 0; offset = 0; };
|
||||||
void* do_allocate(size_t bytes, size_t alignment) override;
|
void* do_allocate(size_t bytes, size_t alignment) override;
|
||||||
void do_deallocate(void* p, size_t bytes, size_t alignment) override {};
|
void do_deallocate(void* p, size_t bytes, size_t alignment) override {};
|
||||||
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; };
|
bool do_is_equal(const memory_resource& other) const noexcept override { return this == &other; };
|
||||||
};
|
};
|
||||||
class FrameAllocatorPool : public std::pmr::memory_resource {
|
class FrameAllocatorPool : public memory_resource {
|
||||||
public:
|
public:
|
||||||
FrameAllocatorPool(size_t allocatorSize = 1024 * 1024) noexcept : allocatorSize(allocatorSize) {}
|
FrameAllocatorPool(size_t allocatorSize = 1024 * 1024) noexcept : allocatorSize(allocatorSize) {}
|
||||||
|
|
||||||
@ -33,17 +35,30 @@ namespace pmr {
|
|||||||
void reset();
|
void reset();
|
||||||
void* do_allocate(size_t bytes, size_t alignment) override { return allocate(bytes, alignment); }
|
void* do_allocate(size_t bytes, size_t alignment) override { return allocate(bytes, alignment); }
|
||||||
void do_deallocate(void* p, size_t bytes, size_t alignment) override {};
|
void do_deallocate(void* p, size_t bytes, size_t alignment) override {};
|
||||||
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return this == &other; };
|
bool do_is_equal(const memory_resource& other) const noexcept override { return this == &other; };
|
||||||
private:
|
private:
|
||||||
size_t allocatorSize;
|
size_t allocatorSize;
|
||||||
std::vector<FrameAllocator> allocators{};
|
vector<FrameAllocator> allocators{};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 自定义的new操作符
|
// 自定义的new操作符
|
||||||
inline void* operator new(size_t size, pmr::FrameAllocatorPool& pool, size_t alignment = alignof(std::max_align_t)) {
|
inline void* operator new(size_t size, pmr::FrameAllocatorPool* pool, size_t alignment = alignof(std::max_align_t)) {
|
||||||
size = (size + alignment - 1) & ~(alignment - 1);
|
size = (size + alignment - 1) & ~(alignment - 1);
|
||||||
return pool.allocate(size, alignment);
|
return pool->allocate(size, alignment);
|
||||||
}
|
}
|
||||||
#include "frame_allocator.inl"
|
#include "frame_allocator.inl"
|
||||||
inline static pmr::FrameAllocatorPool MemPool{};
|
|
||||||
inline static pmr::FrameAllocatorPool FramePool{};
|
inline pmr::FrameAllocatorPool* MemPool{};
|
||||||
|
inline pmr::FrameAllocatorPool* FramePool{};
|
||||||
|
inline pmr::FrameAllocatorPool* GetMemPool(){
|
||||||
|
if(!MemPool){
|
||||||
|
MemPool = new pmr::FrameAllocatorPool();
|
||||||
|
}
|
||||||
|
return MemPool;
|
||||||
|
}
|
||||||
|
inline pmr::FrameAllocatorPool* GetFramePool(){
|
||||||
|
if(!FramePool){
|
||||||
|
FramePool = new(GetMemPool()) pmr::FrameAllocatorPool();
|
||||||
|
}
|
||||||
|
return FramePool;
|
||||||
|
}
|
||||||
@ -3,6 +3,9 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
namespace pmr
|
namespace pmr
|
||||||
{
|
{
|
||||||
|
template<typename T1, typename T2, typename Hasher = std::hash<T1>>
|
||||||
|
using table = std::pmr::unordered_map<T1, T2, Hasher>;
|
||||||
|
using std::pmr::string;
|
||||||
static consteval inline size_t InvalidValue() noexcept { return static_cast<size_t>(-1); }
|
static consteval inline size_t InvalidValue() noexcept { return static_cast<size_t>(-1); }
|
||||||
constexpr inline size_t string_hash(std::string_view str) noexcept;
|
constexpr inline size_t string_hash(std::string_view str) noexcept;
|
||||||
struct Name {
|
struct Name {
|
||||||
@ -17,6 +20,7 @@ namespace pmr
|
|||||||
Name(const char(&str)[N]) noexcept;
|
Name(const char(&str)[N]) noexcept;
|
||||||
Name(const char* str)noexcept;
|
Name(const char* str)noexcept;
|
||||||
Name(const std::string& str)noexcept;
|
Name(const std::string& str)noexcept;
|
||||||
|
Name(const string& str)noexcept;
|
||||||
Name(std::string_view str)noexcept;
|
Name(std::string_view str)noexcept;
|
||||||
auto operator<=>(const Name& other) const noexcept { return hash <=> other.hash; };
|
auto operator<=>(const Name& other) const noexcept { return hash <=> other.hash; };
|
||||||
bool operator==(const Name& other) const {return hash == other.hash;}
|
bool operator==(const Name& other) const {return hash == other.hash;}
|
||||||
@ -32,6 +36,7 @@ namespace pmr
|
|||||||
std::string_view value;
|
std::string_view value;
|
||||||
public:
|
public:
|
||||||
constexpr CName() noexcept : hash{ InvalidValue() } {}
|
constexpr CName() noexcept : hash{ InvalidValue() } {}
|
||||||
|
constexpr CName(string str) noexcept : hash{ string_hash(str) }, value(str) {}
|
||||||
constexpr CName(std::string_view str) noexcept : hash{ string_hash(str) } ,value(str) {}
|
constexpr CName(std::string_view str) noexcept : hash{ string_hash(str) } ,value(str) {}
|
||||||
template<size_t N>
|
template<size_t N>
|
||||||
constexpr CName(const char(&str)[N]) noexcept : hash{ string_hash(str) }, value(str) {}
|
constexpr CName(const char(&str)[N]) noexcept : hash{ string_hash(str) }, value(str) {}
|
||||||
@ -47,7 +52,7 @@ namespace pmr
|
|||||||
std::string ToString() const { return std::string(value); };
|
std::string ToString() const { return std::string(value); };
|
||||||
operator std::string() const { return ToString(); }
|
operator std::string() const { return ToString(); }
|
||||||
};
|
};
|
||||||
bool operator==(const CName& cname, const Name& name) {
|
inline bool operator==(const CName& cname, const Name& name) {
|
||||||
return cname.Hash() == name.Hash();
|
return cname.Hash() == name.Hash();
|
||||||
}
|
}
|
||||||
constexpr inline size_t string_hash(std::string_view str) noexcept
|
constexpr inline size_t string_hash(std::string_view str) noexcept
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
|
#include "name.h"
|
||||||
namespace pmr {
|
namespace pmr {
|
||||||
using NameTable_t = std::pmr::unordered_map<size_t,const std::pmr::string>;
|
using NameTable_t = table<size_t,const string>;
|
||||||
struct NameTable {
|
struct NameTable {
|
||||||
static const std::pmr::string& Find(size_t id);
|
static const string& Find(size_t id);
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static std::string_view MakePair(size_t id, T&& str);
|
static std::string_view MakePair(size_t id, T&& str);
|
||||||
static NameTable_t BuildNameTable() {
|
static NameTable_t& TableRef() {
|
||||||
static FrameAllocatorPool MemPool;
|
static NameTable_t Table{GetMemPool()};
|
||||||
return NameTable_t(&MemPool);
|
return Table;
|
||||||
}
|
}
|
||||||
inline static NameTable_t Table = BuildNameTable();
|
|
||||||
};
|
};
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline std::string_view NameTable::MakePair(size_t id, T&& str)
|
inline std::string_view NameTable::MakePair(size_t id, T&& str)
|
||||||
{
|
{
|
||||||
|
auto& Table = TableRef();
|
||||||
auto it = Table.find(id);
|
auto it = Table.find(id);
|
||||||
if (it == Table.end()) {
|
if (it == Table.end()) {
|
||||||
auto it2 = Table.emplace(std::make_pair(id, std::pmr::string(str, Table.get_allocator())));
|
auto it2 = Table.emplace(std::make_pair(id, string(str, Table.get_allocator())));
|
||||||
if (it2.second) {
|
if (it2.second) {
|
||||||
return it2.first->second;
|
return it2.first->second;
|
||||||
}
|
}
|
||||||
@ -23,11 +24,12 @@ namespace pmr {
|
|||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
inline const std::pmr::string& NameTable::Find(size_t id)
|
inline const string& NameTable::Find(size_t id)
|
||||||
{
|
{
|
||||||
|
auto& Table = TableRef();
|
||||||
auto it = Table.find(id);
|
auto it = Table.find(id);
|
||||||
if (it == Table.end()) {
|
if (it == Table.end()) {
|
||||||
static std::pmr::string empty("", Table.get_allocator());
|
static string empty("", Table.get_allocator());
|
||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
@ -64,6 +66,10 @@ namespace pmr {
|
|||||||
{
|
{
|
||||||
MAKE_NAME_PAIR(hash, str);
|
MAKE_NAME_PAIR(hash, str);
|
||||||
}
|
}
|
||||||
|
inline Name::Name(const string& str)noexcept : hash(string_hash(str))
|
||||||
|
{
|
||||||
|
MAKE_NAME_PAIR(hash, str);
|
||||||
|
}
|
||||||
#undef MAKE_NAME_PAIR
|
#undef MAKE_NAME_PAIR
|
||||||
#undef NAME_TO_STRING
|
#undef NAME_TO_STRING
|
||||||
}
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
#include "module/module.h"
|
#include "module/module.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
class VulkanModule : public api::IDynamicModule
|
class VULKAN_API VulkanModule : public api::IDynamicModule
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void OnLoad(int argc, char** argv) override;
|
void OnLoad(int argc, char** argv) override;
|
||||||
void OnUnload() override;
|
void OnUnload() override;
|
||||||
const char* MetaData(void) override;
|
void InitMetaData(void) override;
|
||||||
};
|
};
|
||||||
MODULE_DEPENDENCY(core, asset, {public = true})
|
IMPLEMENT_DYNAMIC_MODULE(VULKAN_API, VulkanModule, vulkan)
|
||||||
IMPLEMENT_DYNAMIC_MODULE(VulkanModule, vulkan)
|
#include "vulkan.plugin.inl"
|
||||||
@ -8,8 +8,3 @@ void VulkanModule::OnLoad(int argc, char** argv)
|
|||||||
void VulkanModule::OnUnload()
|
void VulkanModule::OnUnload()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* VulkanModule::MetaData(void)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
{"flag":0,"name":"vulkan"}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
shared_component("vulkan","engine")
|
shared_module("vulkan","engine")
|
||||||
add_includedirs("include/vulkan")
|
add_includedirs("include/vulkan")
|
||||||
add_headerfiles("include/**.h")
|
add_headerfiles("include/**.h")
|
||||||
add_files("src/**.cpp")
|
add_files("src/**.cpp")
|
||||||
add_rules("engine.plugin", {file = "include/vulkan/module.h"})
|
add_dependency("core", "asset", {public = true})
|
||||||
@ -16,13 +16,36 @@ function static_component(name, owner, opt)
|
|||||||
set_group("Engine/"..owner.."__comp")
|
set_group("Engine/"..owner.."__comp")
|
||||||
add_includedirs("include", {public = true})
|
add_includedirs("include", {public = true})
|
||||||
end
|
end
|
||||||
function shared_component(name, owner, opt)
|
function shared_module(name, owner, opt)
|
||||||
target(owner)
|
|
||||||
add_deps(name, { public = opt and opt.public or true })
|
|
||||||
target_end()
|
|
||||||
target(name)
|
target(name)
|
||||||
set_kind("shared")
|
set_kind("shared")
|
||||||
set_group("Engine/"..owner.."__comp")
|
set_group("Engine/"..owner.."__dyn")
|
||||||
|
add_rules("engine.api")
|
||||||
add_includedirs("include", {public = true})
|
add_includedirs("include", {public = true})
|
||||||
|
add_rules("engine.plugin", {file = opt and opt.file or "include/" .. name .. "/module.h"})
|
||||||
|
end
|
||||||
|
function add_dependency(...)
|
||||||
|
add_deps(...)
|
||||||
|
local args = {...}
|
||||||
|
local opt = args[#args]
|
||||||
|
if type(opt) ~= "table" or not opt.public then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _,v in ipairs(args) do
|
||||||
|
if v ~= opt then
|
||||||
|
add_values("module.public_dependencies", v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function game_instance(name, opt)
|
||||||
|
target(name)
|
||||||
|
set_kind("shared")
|
||||||
|
set_group("Games")
|
||||||
|
add_rules("engine.api")
|
||||||
|
add_rules("engine.plugin", {file = opt and opt.file or "src/" .. name .. ".h"})
|
||||||
|
target(name .. "-editor")
|
||||||
|
set_kind("binary")
|
||||||
|
set_group("Games")
|
||||||
|
add_deps(name)
|
||||||
end
|
end
|
||||||
includes("**/xmake.lua")
|
includes("**/xmake.lua")
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
std::string_view module_macro[] = { MODULE_DEPENDENCY , IMPLEMENT_DYNAMIC_MODULE , IMPLEMENT_STATIC_MODULE };
|
std::string_view module_macro[] = { PUBLIC_MODULE_DEPENDENCY, PRIVATE_MODULE_DEPENDENCY , IMPLEMENT_DYNAMIC_MODULE, EXPORT_MODULE };
|
||||||
pmr::vector<pmr::string> parseArgs(std::string_view& str) {
|
pmr::vector<pmr::string> parseArgs(std::string_view& str) {
|
||||||
pmr::vector<pmr::string> args(&pool);
|
pmr::vector<pmr::string> args(&pool);
|
||||||
std::stack<char> stack;
|
std::stack<char> stack;
|
||||||
@ -75,20 +75,25 @@ std::optional<MacroData> parseLine(std::string_view line) {
|
|||||||
return std::optional<MacroData>{};
|
return std::optional<MacroData>{};
|
||||||
}
|
}
|
||||||
// 读取文件并返回每一行内容
|
// 读取文件并返回每一行内容
|
||||||
pmr::vector<MacroData> readMacroFile(const char* file_path) {
|
void readMacroFile(const char* file_path, ParseData& pd) {
|
||||||
pmr::vector<MacroData> lines(&pool);
|
|
||||||
std::ifstream file(file_path);
|
std::ifstream file(file_path);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
//std::cerr << "Failed to open file: " << file_path << std::endl;
|
//std::cerr << "Failed to open file: " << file_path << std::endl;
|
||||||
return lines;
|
return;
|
||||||
}
|
}
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(file, line)) {
|
||||||
std::string_view line_view(line);
|
std::string_view line_view(line);
|
||||||
if (auto md = parseLine(line_view)) {
|
if (auto md = parseLine(line_view)) {
|
||||||
lines.push_back(md.value());
|
const char* macro = md.value().macro;
|
||||||
|
if (macro == EXPORT_MODULE) {
|
||||||
|
std::string_view text = md.value().args[0];
|
||||||
|
text = text.substr(3, text.size() - 5);//R(" + )" = 3 + 2 = 5;
|
||||||
|
pd.exportText = text;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pd.mdList.push_back(md.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
return lines;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,20 @@ namespace pmr {
|
|||||||
using std::pmr::string;
|
using std::pmr::string;
|
||||||
}
|
}
|
||||||
inline pmr::monotonic_buffer_resource pool;
|
inline pmr::monotonic_buffer_resource pool;
|
||||||
inline const char* MODULE_DEPENDENCY = "MODULE_DEPENDENCY";
|
inline const char* PUBLIC_MODULE_DEPENDENCY = "PUBLIC_MODULE_DEPENDENCY";
|
||||||
|
inline const char* PRIVATE_MODULE_DEPENDENCY = "PRIVATE_MODULE_DEPENDENCY";
|
||||||
inline const char* IMPLEMENT_DYNAMIC_MODULE = "IMPLEMENT_DYNAMIC_MODULE";
|
inline const char* IMPLEMENT_DYNAMIC_MODULE = "IMPLEMENT_DYNAMIC_MODULE";
|
||||||
inline const char* IMPLEMENT_STATIC_MODULE = "IMPLEMENT_STATIC_MODULE";
|
inline const char* EXPORT_MODULE = "EXPORT_MODULE";
|
||||||
struct MacroData {
|
struct MacroData {
|
||||||
const char* macro{ nullptr };
|
const char* macro{ nullptr };
|
||||||
pmr::vector<pmr::string> args;
|
pmr::vector<pmr::string> args;
|
||||||
MacroData() :args(&pool) {}
|
MacroData() :args(&pool) {}
|
||||||
MacroData(const pmr::vector<pmr::string>& args) :args(args) {}
|
MacroData(const pmr::vector<pmr::string>& args) :args(args) {}
|
||||||
|
operator bool() const { return macro; }
|
||||||
};
|
};
|
||||||
pmr::vector<MacroData> readMacroFile(const char* file_path);
|
struct ParseData {
|
||||||
|
pmr::string moduleFile;
|
||||||
|
pmr::string exportText;
|
||||||
|
pmr::vector<MacroData> mdList;
|
||||||
|
};
|
||||||
|
void readMacroFile(const char* file_path, ParseData& pd);
|
||||||
@ -4,6 +4,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
std::string_view readFile(const char* file_path) {
|
std::string_view readFile(const char* file_path) {
|
||||||
std::ifstream file(file_path, std::ios::ate);
|
std::ifstream file(file_path, std::ios::ate);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
@ -24,47 +25,46 @@ void genLua(const char* file_path, const pmr::vector<MacroData>& mdList) {
|
|||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "{\n";
|
oss << "{\n";
|
||||||
for (auto& md : mdList) {
|
for (auto& md : mdList) {
|
||||||
if (md.macro == MODULE_DEPENDENCY) {
|
if (md.macro == PUBLIC_MODULE_DEPENDENCY || md.macro == PRIVATE_MODULE_DEPENDENCY) {
|
||||||
oss << "\t{";
|
oss << "\t{";
|
||||||
for (auto& args : md.args) {
|
for (auto& args : md.args) {
|
||||||
if (args[0] != '{') {
|
oss << '"' << args << "\", ";
|
||||||
oss << '"' << args << "\", ";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
oss << args;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
oss << "},\n";
|
oss << (md.macro == PUBLIC_MODULE_DEPENDENCY ? "{public = true}},\n" : "{public = false}},\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
oss << '}';
|
oss << '}';
|
||||||
writeFile(file_path, oss.str());
|
writeFile(file_path, oss.str());
|
||||||
}
|
}
|
||||||
void genPlugin(const char* file_path, const pmr::vector<MacroData>& mdList) {
|
void genPlugin(const char* file_path, ParseData& pd) {
|
||||||
std::string_view text = readFile(file_path);
|
readMacroFile(file_path, pd);
|
||||||
api::ModuleInfo info;
|
api::ModuleInfo info;
|
||||||
|
std::string_view text = pd.exportText;
|
||||||
api::JsonDeserialize<api::ModuleInfo>(text, &info);
|
api::JsonDeserialize<api::ModuleInfo>(text, &info);
|
||||||
bool bchange = false;
|
info.dependencies.clear();
|
||||||
for (auto& md : mdList) {
|
for (auto& md : pd.mdList) {
|
||||||
if (md.macro == IMPLEMENT_DYNAMIC_MODULE || md.macro == IMPLEMENT_STATIC_MODULE) {
|
if (md.macro == IMPLEMENT_DYNAMIC_MODULE) {
|
||||||
api::Name name = md.args[1].c_str();
|
info.name = md.args[1];
|
||||||
bchange = info.name != name;
|
|
||||||
info.name = name;
|
|
||||||
}
|
}
|
||||||
else if (md.macro == MODULE_DEPENDENCY) {
|
else if (md.macro == PUBLIC_MODULE_DEPENDENCY) {
|
||||||
for (auto& args : md.args) {
|
for (auto& args : md.args) {
|
||||||
if (args[0] != '{') {
|
std::pmr::string upname{ args, FramePool};
|
||||||
|
std::transform(upname.begin(), upname.end(), upname.begin(), std::toupper);
|
||||||
}
|
info.dependencies.push_back({args, upname + "_VERSION", upname + "_KIND"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bchange) {
|
std::ostringstream oss;
|
||||||
writeFile(file_path, api::JsonSerialize(info));
|
std::string name = info.name.ToString();
|
||||||
}
|
std::transform(name.begin(), name.end(), name.begin(), std::toupper);
|
||||||
|
oss << "#include \"" << pd.moduleFile << "\"\n";
|
||||||
|
oss << "#define EXPORT_MODULE(json)" << name << "_API const char* __module_meta_" << info.name.data() << "__ = json\n";
|
||||||
|
oss << "EXPORT_MODULE(R\"(" << api::JsonSerialize(info) << ")\");\n";
|
||||||
|
writeFile(file_path, oss.str());
|
||||||
}
|
}
|
||||||
|
//已废弃
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 6) {
|
if (argc < 100) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
std::string project_dir = argv[1];
|
std::string project_dir = argv[1];
|
||||||
@ -72,8 +72,10 @@ int main(int argc, char* argv[]) {
|
|||||||
std::string script_dir = argv[3];
|
std::string script_dir = argv[3];
|
||||||
std::string module_file = script_dir + "\\" + argv[4];
|
std::string module_file = script_dir + "\\" + argv[4];
|
||||||
std::string plugin_file = script_dir + "\\" + argv[5];
|
std::string plugin_file = script_dir + "\\" + argv[5];
|
||||||
auto mdList = readMacroFile(module_file.c_str());
|
ParseData pd;
|
||||||
genLua(lua_file.c_str(), mdList);
|
pd.moduleFile = argv[4];
|
||||||
genPlugin(plugin_file.c_str(), mdList);
|
readMacroFile(module_file.c_str(), pd);
|
||||||
|
genLua(lua_file.c_str(), pd.mdList);
|
||||||
|
genPlugin(plugin_file.c_str(), pd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
tool_target("make_plugin")
|
-- tool_target("make_plugin", "shared")
|
||||||
add_includedirs("src")
|
-- add_includedirs("src")
|
||||||
add_files("src/*.cpp")
|
-- add_files("src/*.cpp")
|
||||||
add_headerfiles("src/*.h")
|
-- add_headerfiles("src/*.h")
|
||||||
add_rules("engine.tool")
|
-- add_deps("core")
|
||||||
add_deps("core")
|
-- set_runargs(os.projectdir(), [[build\.gens\vulkan\windows\xmake.lua]],
|
||||||
set_runargs([[F:\engine\zengine]], [[build\.gens\vulkan\windows\xmake.lua]],
|
-- os.projectdir() .. [[\engine\modules\render\vulkan]], [[include\vulkan\module.h]], "vulkan" .. ".plugin.cpp")
|
||||||
[[F:\engine\zengine\engine\modules\render\vulkan]], [[include/vulkan/module.h]], [[vulkan.plugin]])
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
function tool_target(name)
|
function tool_target(name, kind)
|
||||||
target(name)
|
target(name)
|
||||||
set_kind("binary")
|
set_kind(kind or "binary")
|
||||||
set_group("Tools")
|
set_group("Tools")
|
||||||
|
add_rules("engine.tool")
|
||||||
end
|
end
|
||||||
includes("*/xmake.lua")
|
includes("*/xmake.lua")
|
||||||
@ -1,9 +1,5 @@
|
|||||||
import("core.project.depend")
|
import("core.project.depend")
|
||||||
local genList = {}
|
|
||||||
function cmd_compile(genfile, sourcefile, template, macro, define)
|
function cmd_compile(genfile, sourcefile, template, macro, define)
|
||||||
if genList[genfile] then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
import("find_sdk")
|
import("find_sdk")
|
||||||
local meta = find_sdk.find_my_program("refl")
|
local meta = find_sdk.find_my_program("refl")
|
||||||
template = template or path.join(meta.sdkdir, "template")
|
template = template or path.join(meta.sdkdir, "template")
|
||||||
@ -18,7 +14,6 @@ function cmd_compile(genfile, sourcefile, template, macro, define)
|
|||||||
table.insert(argv, "-d")
|
table.insert(argv, "-d")
|
||||||
table.insert(argv, define)
|
table.insert(argv, define)
|
||||||
end
|
end
|
||||||
genList[genfile] = true
|
|
||||||
print("cmd_meta_compile", genfile)
|
print("cmd_meta_compile", genfile)
|
||||||
os.execv(meta.program, argv)
|
os.execv(meta.program, argv)
|
||||||
return argv
|
return argv
|
||||||
@ -26,6 +21,7 @@ end
|
|||||||
|
|
||||||
function _listen_gen_file(target, batch, template, macro, define)
|
function _listen_gen_file(target, batch, template, macro, define)
|
||||||
genfile, sourcefile = batch[1], batch[2]
|
genfile, sourcefile = batch[1], batch[2]
|
||||||
|
sourcefile = string.lower(sourcefile)
|
||||||
local dependfile = target:dependfile(genfile)
|
local dependfile = target:dependfile(genfile)
|
||||||
depend.on_changed(
|
depend.on_changed(
|
||||||
function()
|
function()
|
||||||
@ -33,7 +29,7 @@ function _listen_gen_file(target, batch, template, macro, define)
|
|||||||
end,
|
end,
|
||||||
{dependfile = dependfile, files = sourcefile}
|
{dependfile = dependfile, files = sourcefile}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
function gen(target)
|
function gen(target)
|
||||||
if is_mode("release") then return end
|
if is_mode("release") then return end
|
||||||
local gen_batch = target:data("codegen.batch")
|
local gen_batch = target:data("codegen.batch")
|
||||||
|
|||||||
@ -1,30 +1,43 @@
|
|||||||
import("core.project.depend")
|
import("core.project.depend")
|
||||||
|
local loadTable = {}
|
||||||
function cmd_compile(target, genfile, file)
|
function cmd_compile(target, genfile, file)
|
||||||
if is_mode("release") then return end
|
local name = target:name()
|
||||||
import("find_sdk")
|
if loadTable[name] then
|
||||||
local plugin = find_sdk.find_my_program("make_plugin")
|
return
|
||||||
if not plugin then return end
|
end
|
||||||
print("cmd_compile plugin", genfile, file)
|
loadTable[name] = true
|
||||||
argv = { os.projectdir() , genfile, target:scriptdir(), file, target:name() .. ".plugin"}
|
import("core.project.project")
|
||||||
os.execv(plugin.program, argv)
|
target:data_set("compile", true)
|
||||||
|
local pub_deps = target:values("module.public_dependencies")
|
||||||
|
local cpp_content = "inline void __" .. name .. "__module::InitMetaData(void){\n"
|
||||||
|
cpp_content = cpp_content.."\tmInfo.name = \"" .. name.."\";\n"
|
||||||
|
cpp_content = cpp_content.."\tmInfo.dependencies = {\n "
|
||||||
|
for k,dep in ipairs(pub_deps) do
|
||||||
|
local deptarget = project.target(dep)
|
||||||
|
if deptarget then
|
||||||
|
local kind = deptarget:kind()
|
||||||
|
local version = deptarget:version() or "0.0"
|
||||||
|
cpp_content = cpp_content.."\t\t{\"" .. dep .. "\", \""..version.."\", \""..kind.."\" },\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cpp_content = cpp_content.sub(cpp_content, 1, -3)
|
||||||
|
cpp_content = cpp_content.."\n\t};\n};"
|
||||||
|
print("cmd_compile plugin ", genfile)
|
||||||
|
io.writefile(genfile, cpp_content)
|
||||||
end
|
end
|
||||||
function main(target, file)
|
function main(target, file)
|
||||||
local sourcedir = path.join(target:autogendir({root = true}), target:plat())
|
local sourcedir = path.join(target:autogendir({root = true}), target:plat(), "inl")
|
||||||
if not os.isdir(sourcedir) then
|
if not os.isdir(sourcedir) then
|
||||||
os.mkdir(sourcedir)
|
os.mkdir(sourcedir)
|
||||||
end
|
end
|
||||||
local genfile = path.join(sourcedir,"xmake.lua")
|
target:add("includedirs", sourcedir, {public = true})
|
||||||
|
local genfile = path.join(sourcedir, target:name() .. ".plugin.inl")
|
||||||
local dependfile = target:dependfile(genfile)
|
local dependfile = target:dependfile(genfile)
|
||||||
|
local sourcefile = string.lower(path.join(target:scriptdir(), file))
|
||||||
depend.on_changed(
|
depend.on_changed(
|
||||||
function()
|
function()
|
||||||
cmd_compile(target, genfile, file)
|
cmd_compile(target, genfile, file)
|
||||||
end,
|
end,
|
||||||
{dependfile = dependfile, files = {path.join(target:scriptdir(), file)}}
|
{dependfile = dependfile, files = sourcefile}
|
||||||
)
|
)
|
||||||
if os.exists(genfile) then
|
|
||||||
local dependency = io.load(genfile)
|
|
||||||
for k,v in ipairs(dependency) do
|
|
||||||
target:add("deps", v[1], v[2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@ -4,4 +4,5 @@ rule("engine.plugin")
|
|||||||
import("make_plugin")
|
import("make_plugin")
|
||||||
local file = target:extraconf("rules", "engine.plugin", "file")
|
local file = target:extraconf("rules", "engine.plugin", "file")
|
||||||
make_plugin(target, file or "module.h")
|
make_plugin(target, file or "module.h")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -6,4 +6,10 @@ rule("engine.tool")
|
|||||||
end
|
end
|
||||||
local exefile = target:targetfile()
|
local exefile = target:targetfile()
|
||||||
os.cp(exefile, path.join(tooldir, path.filename(exefile)))
|
os.cp(exefile, path.join(tooldir, path.filename(exefile)))
|
||||||
|
end)
|
||||||
|
rule("engine.api")
|
||||||
|
on_load(function (target)
|
||||||
|
local api = string.upper(target:name()) .. "_API"
|
||||||
|
target:add("defines", api.."=__declspec(dllimport)", {interface=true})
|
||||||
|
target:add("defines", api.."=__declspec(dllexport)", {public=false})
|
||||||
end)
|
end)
|
||||||
@ -8,8 +8,8 @@
|
|||||||
void test(std::string_view str = "") {
|
void test(std::string_view str = "") {
|
||||||
std::cout << "test " << str << std::endl;
|
std::cout << "test " << str << std::endl;
|
||||||
}
|
}
|
||||||
int main() {
|
int main(int argc, char** argv) {
|
||||||
api::ModuleManager::Ptr();
|
api::ModuleManager::Ptr()->MakeGraph("zworld", true, argc, argv);
|
||||||
test("sss");
|
test("sss");
|
||||||
using namespace refl;
|
using namespace refl;
|
||||||
constexpr TStr str1{ "Hello" };
|
constexpr TStr str1{ "Hello" };
|
||||||
@ -36,14 +36,14 @@ int main() {
|
|||||||
std::string s1 = name.ToString();
|
std::string s1 = name.ToString();
|
||||||
std::string s2 = name2.ToString();
|
std::string s2 = name2.ToString();
|
||||||
if (s1.c_str() == s2.c_str()) {
|
if (s1.c_str() == s2.c_str()) {
|
||||||
new(pool)int(1);
|
new(&pool)int(1);
|
||||||
}
|
}
|
||||||
if (s1 == s2) {
|
if (s1 == s2) {
|
||||||
new(pool)int(1);
|
new(&pool)int(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int* a = new(pool)int(1);
|
int* a = new(&pool)int(1);
|
||||||
int* b = new(pool)int(2);
|
int* b = new(&pool)int(2);
|
||||||
int* c = new(pool)int(3);
|
int* c = new(&pool)int(3);
|
||||||
std::cout << "hello engine\n";
|
std::cout << "hello engine\n";
|
||||||
}
|
}
|
||||||
@ -1,5 +1,9 @@
|
|||||||
#include "zworld.h"
|
#include "zworld.h"
|
||||||
|
void ZWorldModule::OnLoad(int argc, char** argv)
|
||||||
|
{
|
||||||
|
|
||||||
void hello() {
|
}
|
||||||
|
|
||||||
}
|
void ZWorldModule::OnUnload()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|||||||
@ -1 +1,11 @@
|
|||||||
void hello();
|
#pragma once
|
||||||
|
#include "module/module.h"
|
||||||
|
class ZWORLD_API ZWorldModule : public api::IDynamicModule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void OnLoad(int argc, char** argv) override;
|
||||||
|
void OnUnload() override;
|
||||||
|
void InitMetaData(void) override;
|
||||||
|
};
|
||||||
|
IMPLEMENT_DYNAMIC_MODULE(ZWORLD_API, ZWorldModule, zworld)
|
||||||
|
#include "zworld.plugin.inl"
|
||||||
@ -1,11 +1,7 @@
|
|||||||
|
game_instance("zworld", "src/zworld.h")
|
||||||
target("zworld")
|
target("zworld")
|
||||||
set_kind("shared")
|
|
||||||
set_group("Games")
|
|
||||||
add_deps("engine", "editor", "vulkan", {public = true})
|
|
||||||
add_files("src/*.cpp")
|
add_files("src/*.cpp")
|
||||||
add_headerfiles("src/*.h")
|
add_headerfiles("src/*.h")
|
||||||
|
add_dependency("engine", "editor", "vulkan", {public = true})
|
||||||
target("zworld-editor")
|
target("zworld-editor")
|
||||||
set_kind("binary")
|
|
||||||
set_group("Games")
|
|
||||||
add_deps("zworld")
|
|
||||||
add_files("editor/main.cpp")
|
add_files("editor/main.cpp")
|
||||||
9
game/zworld/zworld.plugin.cpp
Normal file
9
game/zworld/zworld.plugin.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include "src/zworld.h"
|
||||||
|
void __zworld__module::InitMetaData(void){
|
||||||
|
mInfo.name = "zworld";
|
||||||
|
mInfo.dependencies = {
|
||||||
|
{"engine", "1.0.1", "static" },
|
||||||
|
{"editor", "1.0.1", "static" },
|
||||||
|
{"vulkan", "1.0.1", "shared" }
|
||||||
|
};
|
||||||
|
};
|
||||||
7
module.plugin.inl
Normal file
7
module.plugin.inl
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
inline void __vulkan__module::InitMetaData(void){
|
||||||
|
mInfo.name = "vulkan";
|
||||||
|
mInfo.dependencies = {
|
||||||
|
{"core", "1.0.1", "static" },
|
||||||
|
{"asset", "1.0.1", "static" }
|
||||||
|
};
|
||||||
|
};
|
||||||
8
nil.plugin.inl
Normal file
8
nil.plugin.inl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
inline void __zworld__module::InitMetaData(void){
|
||||||
|
mInfo.name = "zworld";
|
||||||
|
mInfo.dependencies = {
|
||||||
|
{"engine", "1.0.1", "static" },
|
||||||
|
{"editor", "1.0.1", "static" },
|
||||||
|
{"vulkan", "1.0.1", "shared" }
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
add_rules("mode.debug", "mode.release")
|
add_rules("mode.debug", "mode.release")
|
||||||
|
set_version("1.0.1", {soname = true})
|
||||||
set_arch("x64")
|
set_arch("x64")
|
||||||
set_languages("cxx20")
|
set_languages("cxx20")
|
||||||
set_project("zengine")
|
set_project("zengine")
|
||||||
@ -7,4 +8,6 @@ set_runtimes("MD","c++_shared")
|
|||||||
includes("engine")
|
includes("engine")
|
||||||
includes("game/*/xmake.lua")
|
includes("game/*/xmake.lua")
|
||||||
--xmake project -k vsxmake2022 -a x64
|
--xmake project -k vsxmake2022 -a x64
|
||||||
--xmake project -k vsxmake2022 -m "debug;release"
|
--xmake project -k vsxmake2022 -m "debug;release"
|
||||||
|
--xmake build -vD -y -P . "zworld-editor"
|
||||||
|
--xrepo env -b emmylua_debugger -- xmake project -k vsxmake2022 -m "debug;release"
|
||||||
8
zworld.plugin.inl
Normal file
8
zworld.plugin.inl
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
inline void __zworld__module::InitMetaData(void){
|
||||||
|
mInfo.name = "zworld";
|
||||||
|
mInfo.dependencies = {
|
||||||
|
{"engine", "1.0.1", "static" },
|
||||||
|
{"editor", "1.0.1", "static" },
|
||||||
|
{"vulkan", "1.0.1", "shared" }
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user