Engine Single Instance Mode
This commit is contained in:
@ -8,8 +8,6 @@ set(IAEngine_Sources
|
||||
imp/cpp/Random.cpp
|
||||
imp/cpp/Texture.cpp
|
||||
|
||||
imp/cpp/ResourceManager.cpp
|
||||
|
||||
imp/cpp/Rendering/Camera.cpp
|
||||
imp/cpp/Rendering/Renderer.cpp
|
||||
imp/cpp/Rendering/GPUBuffer.cpp
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <IAEngine/Rendering/GPUTexture.hpp>
|
||||
|
||||
#include <IAEngine/Audio.hpp>
|
||||
#include <IAEngine/IAEngine.hpp>
|
||||
#include <IAEngine/Input.hpp>
|
||||
@ -23,31 +25,24 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_sdl3.h>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
struct EngineContext
|
||||
{
|
||||
SDL_Window *Window{};
|
||||
SDL_Event Event{};
|
||||
|
||||
BOOL ShouldClose{false};
|
||||
};
|
||||
} // namespace ia::iae
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
CONSTEXPR FLOAT32 GAME_UPDATE_INTERVAL = 1000.0f / 60.0f;
|
||||
|
||||
Engine::Engine() : m_context(MakeRefPtr<EngineContext>())
|
||||
{
|
||||
}
|
||||
SDL_Event g_event{};
|
||||
FLOAT32 g_updateTimer{};
|
||||
BOOL g_shouldClose{false};
|
||||
SDL_Window *g_windowHandle{};
|
||||
Vector<RefPtr<GPUTexture>> g_gpuTextureRefs;
|
||||
|
||||
RefPtr<Scene> g_activeScene;
|
||||
|
||||
Engine::~Engine()
|
||||
{
|
||||
}
|
||||
|
||||
BOOL Engine::Initialize(IN CONST InitConfig &config)
|
||||
{
|
||||
@ -59,16 +54,17 @@ namespace ia::iae
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(m_context->Window = SDL_CreateWindow(config.GameName.c_str(), config.WindowWidth, config.WindowHeight, SDL_WINDOW_RESIZABLE)))
|
||||
if (!(g_windowHandle = SDL_CreateWindow(config.GameName.c_str(), config.WindowWidth, config.WindowHeight,
|
||||
SDL_WINDOW_RESIZABLE)))
|
||||
{
|
||||
IAE_LOG_ERROR("Couldn't create SDL3 window: ", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_SetWindowResizable(m_context->Window, false);
|
||||
SDL_SetWindowResizable(g_windowHandle, false);
|
||||
|
||||
Time::Initialize();
|
||||
|
||||
if (!Renderer::Initialize(this))
|
||||
if (!Renderer::Initialize())
|
||||
return false;
|
||||
|
||||
Random::Initialize();
|
||||
@ -84,29 +80,30 @@ namespace ia::iae
|
||||
{
|
||||
IAE_LOG_INFO("Shutting down IAEngine");
|
||||
|
||||
m_resourceManager.reset();
|
||||
for (auto &t : g_gpuTextureRefs)
|
||||
t.reset();
|
||||
|
||||
Physics::Terminate();
|
||||
Audio::Terminate();
|
||||
Renderer::Terminate();
|
||||
Audio::Terminate();
|
||||
Physics::Terminate();
|
||||
|
||||
SDL_DestroyWindow(m_context->Window);
|
||||
SDL_DestroyWindow(g_windowHandle);
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
VOID Engine::BeginFrame()
|
||||
{
|
||||
SDL_PollEvent(&m_context->Event);
|
||||
if (m_context->Event.type == SDL_EVENT_QUIT)
|
||||
m_context->ShouldClose = true;
|
||||
SDL_PollEvent(&g_event);
|
||||
if (g_event.type == SDL_EVENT_QUIT)
|
||||
g_shouldClose = true;
|
||||
ProcessEvents();
|
||||
m_updateTimer += Time::GetFrameDeltaTime();
|
||||
if (m_updateTimer >= GAME_UPDATE_INTERVAL)
|
||||
g_updateTimer += Time::GetFrameDeltaTime();
|
||||
if (g_updateTimer >= GAME_UPDATE_INTERVAL)
|
||||
{
|
||||
UpdateGame();
|
||||
while (m_updateTimer >= GAME_UPDATE_INTERVAL)
|
||||
m_updateTimer -= GAME_UPDATE_INTERVAL;
|
||||
while (g_updateTimer >= GAME_UPDATE_INTERVAL)
|
||||
g_updateTimer -= GAME_UPDATE_INTERVAL;
|
||||
}
|
||||
Renderer::BeginFrame();
|
||||
RenderGame();
|
||||
@ -120,41 +117,72 @@ namespace ia::iae
|
||||
|
||||
BOOL Engine::ShouldClose()
|
||||
{
|
||||
return m_context->ShouldClose;
|
||||
return g_shouldClose;
|
||||
}
|
||||
|
||||
VOID Engine::ProcessEvents()
|
||||
{
|
||||
ImGui_ImplSDL3_ProcessEvent(&m_context->Event);
|
||||
Input::OnEvent(&m_context->Event);
|
||||
ImGui_ImplSDL3_ProcessEvent(&g_event);
|
||||
Input::OnEvent(&g_event);
|
||||
}
|
||||
|
||||
VOID Engine::UpdateGame()
|
||||
{
|
||||
Physics::Update();
|
||||
|
||||
if B_LIKELY (m_activeScene)
|
||||
m_activeScene->Update();
|
||||
if B_LIKELY (g_activeScene)
|
||||
g_activeScene->Update();
|
||||
}
|
||||
|
||||
VOID Engine::RenderGame()
|
||||
{
|
||||
if B_LIKELY (m_activeScene)
|
||||
m_activeScene->Draw();
|
||||
if B_LIKELY (g_activeScene)
|
||||
g_activeScene->Draw();
|
||||
}
|
||||
|
||||
VOID Engine::ChangeScene(IN RefPtr<Scene> scene)
|
||||
{
|
||||
m_activeScene = scene;
|
||||
}
|
||||
|
||||
RefPtr<Scene> Engine::CreateScene()
|
||||
{
|
||||
return MakeRefPtr<Scene>(this);
|
||||
}
|
||||
|
||||
PVOID Engine::GetWindowHandle() CONST
|
||||
{
|
||||
return m_context->Window;
|
||||
g_activeScene = scene;
|
||||
}
|
||||
} // namespace ia::iae
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
RefPtr<Scene> Engine::CreateScene()
|
||||
{
|
||||
return MakeRefPtr<Scene>();
|
||||
}
|
||||
|
||||
Texture Engine::CreateTexture(IN CONST Vector<UINT8> &encodedData)
|
||||
{
|
||||
return CreateTexture(encodedData.data(), encodedData.size());
|
||||
}
|
||||
|
||||
Texture Engine::CreateTexture(IN PCUINT8 encodedData, IN SIZE_T encodedDataSize)
|
||||
{
|
||||
INT32 w, h, nrChannels;
|
||||
const auto pixels = stbi_load_from_memory(encodedData, encodedDataSize, &w, &h, &nrChannels, STBI_rgb_alpha);
|
||||
if (!pixels)
|
||||
THROW_INVALID_DATA("Failed to decode the provided image data");
|
||||
const auto result = CreateTexture((PCUINT8) pixels, w, h);
|
||||
STBI_FREE(pixels);
|
||||
return result;
|
||||
}
|
||||
|
||||
Texture Engine::CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height)
|
||||
{
|
||||
const auto t = GPUTexture::Create(rgbaData, width, height);
|
||||
g_gpuTextureRefs.pushBack(t);
|
||||
return Texture(t->GetHandle(), width, height);
|
||||
}
|
||||
|
||||
Sound Engine::CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize)
|
||||
{
|
||||
return Audio::CreateSound(audioData, audioDataSize);
|
||||
}
|
||||
|
||||
Sound Engine::CreateSound(IN CONST Vector<UINT8> &audioData)
|
||||
{
|
||||
return CreateSound(audioData.data(), audioData.size());
|
||||
}
|
||||
} // namespace ia::iae
|
||||
@ -36,47 +36,11 @@ namespace ia::iae
|
||||
|
||||
VOID Node::Draw()
|
||||
{
|
||||
BOOL drew = false;
|
||||
for (auto &n : m_children)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) >= 0)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
|
||||
if(((INT32) GetPosition().z) < 0)
|
||||
{
|
||||
for (auto &c : m_components)
|
||||
c->Draw();
|
||||
}
|
||||
|
||||
for (INT32 i = 0; i < 8; i++) // [IATODO]
|
||||
{
|
||||
for (auto &n : m_children)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) != i)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
if (((INT32) GetPosition().z) == i)
|
||||
{
|
||||
for (auto &c : m_components)
|
||||
c->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &n : m_children)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) < 8)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
|
||||
if(((INT32) GetPosition().z) > 8)
|
||||
{
|
||||
for (auto &c : m_components)
|
||||
c->Draw();
|
||||
}
|
||||
for (auto &c : m_components)
|
||||
c->Draw();
|
||||
}
|
||||
|
||||
VOID Node::Update()
|
||||
|
||||
@ -29,11 +29,12 @@
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
EXTERN SDL_Window *g_windowHandle;
|
||||
|
||||
INT32 Renderer::s_width{};
|
||||
INT32 Renderer::s_height{};
|
||||
Vector<Renderer::DebugUIWindow> Renderer::s_debugUIWindows;
|
||||
|
||||
SDL_Window *g_windowHandle{};
|
||||
SDL_GPUDevice *g_gpuDevice{};
|
||||
|
||||
// Render State
|
||||
@ -53,12 +54,11 @@ namespace ia::iae
|
||||
glm::mat4 matView{1.0f};
|
||||
glm::mat4 matModel{1.0f};
|
||||
|
||||
BOOL Renderer::Initialize(IN Engine *engine)
|
||||
BOOL Renderer::Initialize()
|
||||
{
|
||||
g_windowHandle = (SDL_Window *) engine->GetWindowHandle();
|
||||
SDL_GetWindowSizeInPixels(g_windowHandle, &s_width, &s_height);
|
||||
|
||||
if (!(g_gpuDevice = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, engine->IsDebugMode, nullptr)))
|
||||
if (!(g_gpuDevice = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, Engine::IsDebugMode, nullptr)))
|
||||
{
|
||||
IAE_LOG_ERROR("Couldn't create SDL3 GPU Device: ", SDL_GetError());
|
||||
return false;
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
// IAEngine: 2D Game Engine by IA
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <IAEngine/IAEngine.hpp>
|
||||
#include <IAEngine/Rendering/GPUTexture.hpp>
|
||||
#include <IAEngine/ResourceManager.hpp>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
ResourceManager::ResourceManager(IN Engine *engine) : m_engine(engine)
|
||||
{
|
||||
}
|
||||
|
||||
ResourceManager::~ResourceManager()
|
||||
{
|
||||
for(auto& t: m_textures)
|
||||
t.reset();
|
||||
}
|
||||
|
||||
RefPtr<Texture> ResourceManager::CreateTexture(IN CONST Span<CONST UINT8> &encodedData)
|
||||
{
|
||||
return CreateTexture(encodedData.data(), encodedData.size());
|
||||
}
|
||||
|
||||
RefPtr<Texture> ResourceManager::CreateTexture(IN PCUINT8 encodedData, IN SIZE_T encodedDataSize)
|
||||
{
|
||||
INT32 w, h, nrChannels;
|
||||
const auto pixels = stbi_load_from_memory(encodedData, encodedDataSize, &w, &h, &nrChannels, STBI_rgb_alpha);
|
||||
if (!pixels)
|
||||
THROW_INVALID_DATA("Failed to decode the provided image data");
|
||||
const auto result = CreateTexture((PCUINT8) pixels, w, h);
|
||||
STBI_FREE(pixels);
|
||||
return result;
|
||||
}
|
||||
|
||||
RefPtr<Texture> ResourceManager::CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height)
|
||||
{
|
||||
const auto t = GPUTexture::Create(rgbaData, width, height);
|
||||
m_textures.pushBack(t);
|
||||
return MakeRefPtr<Texture>(t->GetHandle(), width, height);
|
||||
}
|
||||
} // namespace ia::iae
|
||||
@ -18,39 +18,10 @@
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
Scene::Scene(IN Engine *engine) : m_engine(engine)
|
||||
{
|
||||
}
|
||||
|
||||
Scene::~Scene()
|
||||
{
|
||||
}
|
||||
|
||||
VOID Scene::Draw()
|
||||
{
|
||||
for (auto &n : m_nodes)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) >= 0)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
|
||||
for (INT32 i = 0; i < 8; i++) // [IATODO]
|
||||
{
|
||||
for (auto &n : m_nodes)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) != i)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &n : m_nodes)
|
||||
{
|
||||
if (((INT32) n->GetPosition().z) < 8)
|
||||
continue;
|
||||
n->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
VOID Scene::Update()
|
||||
|
||||
@ -17,13 +17,17 @@
|
||||
#include <IAEngine/IAEngine.hpp>
|
||||
#include <IAEngine/Texture.hpp>
|
||||
|
||||
#include <IAEngine/Rendering/Renderer.hpp>
|
||||
#include <IAEngine/Rendering/Mesh/Quad.hpp>
|
||||
#include <IAEngine/Rendering/Renderer.hpp>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
Texture::Texture(IN Handle handle,IN INT32 width, IN INT32 height):
|
||||
m_handle(handle), m_size({(FLOAT32)width, (FLOAT32)height, 1.0f})
|
||||
Texture::Texture()
|
||||
{
|
||||
}
|
||||
|
||||
Texture::Texture(IN Handle handle, IN INT32 width, IN INT32 height)
|
||||
: m_handle(handle), m_size({(FLOAT32) width, (FLOAT32) height, 1.0f})
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -16,16 +16,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IAEngine/Nodes/Node.hpp>
|
||||
#include <IAEngine/Audio.hpp>
|
||||
#include <IAEngine/Texture.hpp>
|
||||
#include <IAEngine/Rendering/Renderer.hpp>
|
||||
#include <IAEngine/ResourceManager.hpp>
|
||||
#include <IAEngine/Scene.hpp>
|
||||
#include <IAEngine/Rendering/Renderer.hpp>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
struct EngineContext;
|
||||
|
||||
class Engine
|
||||
{
|
||||
public:
|
||||
@ -43,41 +40,28 @@ namespace ia::iae
|
||||
#endif
|
||||
|
||||
public:
|
||||
Engine();
|
||||
~Engine();
|
||||
STATIC BOOL Initialize(IN CONST InitConfig &config);
|
||||
STATIC VOID Terminate();
|
||||
|
||||
BOOL Initialize(IN CONST InitConfig &config);
|
||||
VOID Terminate();
|
||||
STATIC VOID BeginFrame();
|
||||
STATIC VOID EndFrame();
|
||||
STATIC BOOL ShouldClose();
|
||||
|
||||
VOID BeginFrame();
|
||||
VOID EndFrame();
|
||||
BOOL ShouldClose();
|
||||
|
||||
template<typename _class_type> _class_type *RegisterResourceManager();
|
||||
STATIC VOID ChangeScene(IN RefPtr<Scene> scene);
|
||||
|
||||
public:
|
||||
RefPtr<Scene> CreateScene();
|
||||
STATIC RefPtr<Scene> CreateScene();
|
||||
|
||||
VOID ChangeScene(IN RefPtr<Scene> scene);
|
||||
|
||||
public:
|
||||
PVOID GetWindowHandle() CONST;
|
||||
STATIC Texture CreateTexture(IN CONST Vector<UINT8> &encodedData);
|
||||
STATIC Texture CreateTexture(IN PCUINT8 encodedData, IN SIZE_T encodedDataSize);
|
||||
STATIC Texture CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height);
|
||||
|
||||
STATIC Sound CreateSound(IN CONST Vector<UINT8> &audioData);
|
||||
STATIC Sound CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize);
|
||||
|
||||
private:
|
||||
VOID ProcessEvents();
|
||||
VOID UpdateGame();
|
||||
VOID RenderGame();
|
||||
|
||||
private:
|
||||
FLOAT32 m_updateTimer{};
|
||||
RefPtr<Scene> m_activeScene{};
|
||||
CONST RefPtr<EngineContext> m_context;
|
||||
RefPtr<ResourceManager> m_resourceManager;
|
||||
STATIC VOID ProcessEvents();
|
||||
STATIC VOID UpdateGame();
|
||||
STATIC VOID RenderGame();
|
||||
};
|
||||
|
||||
template<typename _class_type> _class_type *Engine::RegisterResourceManager()
|
||||
{
|
||||
m_resourceManager = MakeRefPtr<_class_type>(this);
|
||||
return (_class_type *) m_resourceManager.get();
|
||||
}
|
||||
} // namespace ia::iae
|
||||
@ -20,7 +20,6 @@
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
class Engine;
|
||||
class Camera2D;
|
||||
|
||||
class Renderer
|
||||
@ -34,7 +33,7 @@ namespace ia::iae
|
||||
};
|
||||
|
||||
public:
|
||||
STATIC BOOL Initialize(IN Engine *engine);
|
||||
STATIC BOOL Initialize();
|
||||
STATIC VOID Terminate();
|
||||
|
||||
public:
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
// IAEngine: 2D Game Engine by IA
|
||||
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IAEngine/Audio.hpp>
|
||||
#include <IAEngine/Texture.hpp>
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
class Engine;
|
||||
|
||||
class ResourceManager
|
||||
{
|
||||
public:
|
||||
ResourceManager(IN Engine *engine);
|
||||
~ResourceManager();
|
||||
|
||||
RefPtr<Texture> CreateTexture(IN CONST Span<CONST UINT8> &encodedData);
|
||||
RefPtr<Texture> CreateTexture(IN PCUINT8 encodedData, IN SIZE_T encodedDataSize);
|
||||
RefPtr<Texture> CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height);
|
||||
|
||||
public:
|
||||
Sound CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize)
|
||||
{
|
||||
return Audio::CreateSound(audioData, audioDataSize);
|
||||
}
|
||||
|
||||
Sound CreateSound(IN CONST Vector<UINT8> &audioData)
|
||||
{
|
||||
return CreateSound(audioData.data(), audioData.size());
|
||||
}
|
||||
|
||||
protected:
|
||||
Engine *CONST m_engine;
|
||||
Vector<RefPtr<class GPUTexture>> m_textures;
|
||||
|
||||
friend class Engine;
|
||||
};
|
||||
} // namespace ia::iae
|
||||
@ -20,14 +20,9 @@
|
||||
|
||||
namespace ia::iae
|
||||
{
|
||||
class Engine;
|
||||
|
||||
class Scene
|
||||
{
|
||||
public:
|
||||
Scene(IN Engine* engine);
|
||||
~Scene();
|
||||
|
||||
VOID Draw();
|
||||
VOID Update();
|
||||
|
||||
@ -42,7 +37,6 @@ namespace ia::iae
|
||||
}
|
||||
|
||||
private:
|
||||
Engine* CONST m_engine;
|
||||
Vector<RefPtr<Node>> m_nodes;
|
||||
};
|
||||
} // namespace ia::iae
|
||||
@ -24,6 +24,7 @@ namespace ia::iae
|
||||
{
|
||||
public:
|
||||
Texture(IN Handle handle, IN INT32 width, IN INT32 height);
|
||||
Texture();
|
||||
~Texture();
|
||||
|
||||
public:
|
||||
@ -42,7 +43,7 @@ namespace ia::iae
|
||||
}
|
||||
|
||||
private:
|
||||
CONST Handle m_handle{};
|
||||
CONST glm::vec3 m_size;
|
||||
Handle m_handle{INVALID_HANDLE};
|
||||
glm::vec3 m_size{};
|
||||
};
|
||||
} // namespace ia::iae
|
||||
Reference in New Issue
Block a user