Program Listing for File windowsystem.cpp¶
↰ Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/application/window/windowsystem.cpp
)
#include <application/window/windowsystem.hpp>
#include <rendering/debugrendering.hpp>
namespace legion::application
{
sparse_map<GLFWwindow*, ecs::component_handle<window>> WindowSystem::m_windowComponents;
async::spinlock WindowSystem::m_creationLock;
async::spinlock WindowSystem::m_creationRequestLock;
std::vector<WindowSystem::window_request> WindowSystem::m_creationRequests;
async::spinlock WindowSystem::m_fullscreenRequestLock;
std::vector<WindowSystem::fullscreen_toggle_request> WindowSystem::m_fullscreenRequests;
async::spinlock WindowSystem::m_iconRequestLock;
std::vector<WindowSystem::icon_request> WindowSystem::m_iconRequests;
void WindowSystem::closeWindow(GLFWwindow* window)
{
if (!ContextHelper::initialized())
return;
{
std::lock_guard guard(m_creationLock); // Lock all creation sensitive data.
auto handle = m_windowComponents[window];
async::spinlock* lock = nullptr;
if (handle.valid())
{
m_eventBus->raiseEvent<window_close>(m_windowComponents[window]); // Trigger any callbacks that want to know about any windows closing.
if (!ContextHelper::windowShouldClose(window)) // If a callback canceled the window destruction then we should cancel.
return;
{
lock = handle.read().lock;
std::lock_guard guard(*lock); // "deleting" the window is technically writing, so we copy the pointer and use that to lock it.
handle.write(invalid_window); // We mark the window as deleted without deleting it yet. It can cause users to find invalid windows,
// but at least they won't use a destroyed component after the lock unlocks.
handle.destroy();
m_windowComponents.erase(window);
}
if (handle.entity.get_id() == world_entity_id)
{
m_eventBus->raiseEvent<events::exit>(); // If the current window we're closing is the main window we want to close the application.
} // (we might want to leave this up to the user at some point.)
ContextHelper::destroyWindow(window); // After all traces of the window throughout the engine have been erased we actually close the window.
}
if (lock)
delete lock;
}
}
void WindowSystem::onWindowMoved(GLFWwindow* window, int x, int y)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_move>(m_windowComponents[window], math::ivec2(x, y));
}
void WindowSystem::onWindowResize(GLFWwindow* win, int width, int height)
{
if (m_windowComponents.contains(win))
{
auto wincomp = m_windowComponents[win].read();
wincomp.m_size = math::ivec2(width, height);
m_windowComponents[win].write(wincomp);
m_eventBus->raiseEvent<window_resize>(m_windowComponents[win], wincomp.m_size);
}
}
void WindowSystem::onWindowRefresh(GLFWwindow* window)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_refresh>(m_windowComponents[window]);
}
void WindowSystem::onWindowFocus(GLFWwindow* window, int focused)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_focus>(m_windowComponents[window], focused);
}
void WindowSystem::onWindowIconify(GLFWwindow* window, int iconified)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_iconified>(m_windowComponents[window], iconified);
}
void WindowSystem::onWindowMaximize(GLFWwindow* window, int maximized)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_maximized>(m_windowComponents[window], maximized);
}
void WindowSystem::onWindowFrameBufferResize(GLFWwindow* window, int width, int height)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_framebuffer_resize>(m_windowComponents[window], math::ivec2(width, height));
}
void WindowSystem::onWindowContentRescale(GLFWwindow* window, float xscale, float yscale)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_content_rescale>(m_windowComponents[window], math::fvec2(xscale, xscale));
}
void WindowSystem::onItemDroppedInWindow(GLFWwindow* window, int count, const char** paths)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<window_item_dropped>(m_windowComponents[window], count, paths);
}
void WindowSystem::onMouseEnterWindow(GLFWwindow* window, int entered)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<mouse_enter_window>(m_windowComponents[window], entered);
}
void WindowSystem::onKeyInput(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<key_input>(m_windowComponents[window], key, scancode, action, mods);
}
void WindowSystem::onCharInput(GLFWwindow* window, uint codepoint)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<char_input>(m_windowComponents[window], codepoint);
}
void WindowSystem::onMouseMoved(GLFWwindow* window, double xpos, double ypos)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<mouse_moved>(m_windowComponents[window], math::dvec2(xpos, ypos) / (math::dvec2)ContextHelper::getFramebufferSize(window));
}
void WindowSystem::onMouseButton(GLFWwindow* window, int button, int action, int mods)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<mouse_button>(m_windowComponents[window], button, action, mods);
}
void WindowSystem::onMouseScroll(GLFWwindow* window, double xoffset, double yoffset)
{
if (m_windowComponents.contains(window))
m_eventBus->raiseEvent<mouse_scrolled>(m_windowComponents[window], math::dvec2(xoffset, yoffset));
}
void WindowSystem::onExit(events::exit* event)
{
std::lock_guard guard(m_creationLock);
m_windowQuery.queryEntities();
for (auto entity : m_windowQuery)
{
auto handle = entity.get_component_handle<window>();
window win = handle.read();
async::spinlock* lock = nullptr;
if (handle.valid())
{
m_eventBus->raiseEvent<window_close>(handle); // Trigger any callbacks that want to know about any windows closing.
{
lock = handle.read().lock;
std::lock_guard guard(*lock); // "deleting" the window is technically writing, so we copy the pointer and use that to lock it.
handle.write(invalid_window); // We mark the window as deleted without deleting it yet. It can cause users to find invalid windows,
// but at least they won't use a destroyed component after the lock unlocks.
handle.destroy();
m_windowComponents.erase(win);
}
ContextHelper::destroyWindow(win); // After all traces of the window throughout the engine have been erased we actually close the window.
}
if (lock)
delete lock;
}
m_exit = true;
ContextHelper::terminate();
}
bool WindowSystem::windowStillExists(GLFWwindow* win)
{
return m_windowComponents.contains(win);
}
void WindowSystem::requestIconChange(id_type entityId, image_handle icon)
{
if (entityId)
{
std::lock_guard guard(m_iconRequestLock);
m_iconRequests.emplace_back(entityId, icon);
}
else
log::warn("Icon change denied, invalid entity given.");
}
void WindowSystem::requestIconChange(id_type entityId, const std::string& iconName)
{
if (entityId)
{
std::lock_guard guard(m_iconRequestLock);
m_iconRequests.emplace_back(entityId, iconName);
}
else
log::warn("Icon change denied, invalid entity given.");
}
void WindowSystem::requestFullscreenToggle(id_type entityId, math::ivec2 position, math::ivec2 size)
{
if (entityId)
{
std::lock_guard guard(m_fullscreenRequestLock);
m_fullscreenRequests.emplace_back(entityId, position, size);
}
else
log::warn("Fullscreen toggle denied, invalid entity given.");
}
void WindowSystem::requestWindow(id_type entityId, math::ivec2 size, const std::string& name, image_handle icon, GLFWmonitor* monitor, GLFWwindow* share, int swapInterval, const std::vector<std::pair<int, int>>& hints)
{
if (entityId)
{
std::lock_guard guard(m_creationRequestLock);
m_creationRequests.emplace_back(entityId, size, name, icon, monitor, share, swapInterval, hints);
}
else
log::warn("Window creation denied, invalid entity given.");
}
void WindowSystem::requestWindow(id_type entityId, math::ivec2 size, const std::string& name, image_handle icon, GLFWmonitor* monitor, GLFWwindow* share, int swapInterval)
{
if (entityId)
{
std::lock_guard guard(m_creationRequestLock);
m_creationRequests.emplace_back(entityId, size, name, icon, monitor, share, swapInterval);
}
else
log::warn("Window creation denied, invalid entity given.");
}
void WindowSystem::requestWindow(id_type entityId, math::ivec2 size, const std::string& name, const std::string& iconName, GLFWmonitor* monitor, GLFWwindow* share, int swapInterval, const std::vector<std::pair<int, int>>& hints)
{
if (entityId)
{
std::lock_guard guard(m_creationRequestLock);
m_creationRequests.emplace_back(entityId, size, name, iconName, monitor, share, swapInterval);
}
else
log::warn("Window creation denied, invalid entity given.");
}
void WindowSystem::requestWindow(id_type entityId, math::ivec2 size, const std::string& name, const std::string& iconName, GLFWmonitor* monitor, GLFWwindow* share, int swapInterval)
{
if (entityId)
{
std::lock_guard guard(m_creationRequestLock);
m_creationRequests.emplace_back(entityId, size, name, iconName, monitor, share, swapInterval);
}
else
log::warn("Window creation denied, invalid entity given.");
}
void WindowSystem::setup()
{
using namespace filesystem::literals;
m_defaultIcon = ImageCache::create_image("Legion Icon", "engine://resources/legion/icon"_view, { channel_format::eight_bit, image_components::rgba, false });
m_windowQuery = createQuery<window>();
bindToEvent<events::exit, &WindowSystem::onExit>();
if (m_creationRequests.empty() || (std::find_if(m_creationRequests.begin(), m_creationRequests.end(), [](window_request& r) { return r.entityId == world_entity_id; }) == m_creationRequests.end()))
requestWindow(world_entity_id, math::ivec2(1360, 768), "LEGION Engine", invalid_image_handle, nullptr, nullptr, 1); // Create the request for the main window.
m_scheduler->sendCommand(m_scheduler->getChainThreadId("Input"), [&]() // We send a command to the input thread before the input process chain starts.
{ // This way we can create the main window before the rest of the engine get initialised.
if (!ContextHelper::initialized()) // Initialize context.
if (!ContextHelper::init())
{
exit();
return; // If we can't initialize we can't create any windows, not creating the main window means the engine should shut down.
}
log::trace("Creating main window.");
createWindows();
showMainWindow();
});
createProcess<&WindowSystem::refreshWindows>("Rendering");
createProcess<&WindowSystem::handleWindowEvents>("Input");
}
void WindowSystem::createWindows()
{
OPTICK_EVENT();
if (m_exit) // If the engine is exiting then we can't create new windows.
return;
std::lock_guard guard(m_creationRequestLock);
for (auto& request : m_creationRequests)
{
if (!m_ecs->validateEntity(request.entityId))
{
log::warn("Window creation denied, entity {} doesn't exist.", request.entityId);
continue;
}
log::trace("creating a window");
if (request.hints.size())
{
for (auto& [hint, value] : request.hints)
ContextHelper::windowHint(hint, value);
}
else // Default window hints.
{
ContextHelper::windowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
ContextHelper::windowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
ContextHelper::windowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE);
ContextHelper::windowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWmonitor* monitor = request.monitor;
if (!request.monitor)
monitor = ContextHelper::getPrimaryMonitor();
const GLFWvidmode* mode = ContextHelper::getVideoMode(monitor);
ContextHelper::windowHint(GLFW_RED_BITS, mode->redBits);
ContextHelper::windowHint(GLFW_GREEN_BITS, mode->greenBits);
ContextHelper::windowHint(GLFW_BLUE_BITS, mode->blueBits);
ContextHelper::windowHint(GLFW_REFRESH_RATE, mode->refreshRate);
}
if (request.size == math::ivec2(0, 0))
request.size = { 400, 400 };
if (request.name.empty())
request.name = "LEGION Engine";
image_handle icon = request.icon;
if (icon == invalid_image_handle)
icon = m_defaultIcon;
ContextHelper::windowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
window win = ContextHelper::createWindow(request.size, request.name.c_str(), request.monitor, request.share);
auto [lock, image] = icon.get_raw_image();
{
async::readonly_guard guard(lock);
if (image.components == image_components::rgba && image.format == channel_format::eight_bit)
{
GLFWimage icon{ image.size.x, image.size.y, image.get_raw_data<byte>() };
ContextHelper::setWindowIcon(win, 1, &icon);
}
}
win.m_title = request.name;
win.m_isFullscreen = (request.monitor != nullptr);
win.m_swapInterval = request.swapInterval;
win.m_size = request.size;
auto setCallbacks = [](const window& win)
{
ContextHelper::makeContextCurrent(win);
ContextHelper::swapInterval(win.m_swapInterval);
ContextHelper::setWindowCloseCallback(win, &WindowSystem::closeWindow);
ContextHelper::setWindowPosCallback(win, &WindowSystem::onWindowMoved);
ContextHelper::setWindowSizeCallback(win, &WindowSystem::onWindowResize);
ContextHelper::setWindowRefreshCallback(win, &WindowSystem::onWindowRefresh);
ContextHelper::setWindowFocusCallback(win, &WindowSystem::onWindowFocus);
ContextHelper::setWindowIconifyCallback(win, &WindowSystem::onWindowIconify);
ContextHelper::setWindowMaximizeCallback(win, &WindowSystem::onWindowMaximize);
ContextHelper::setFramebufferSizeCallback(win, &WindowSystem::onWindowFrameBufferResize);
ContextHelper::setWindowContentScaleCallback(win, &WindowSystem::onWindowContentRescale);
ContextHelper::setDropCallback(win, &WindowSystem::onItemDroppedInWindow);
ContextHelper::setCursorEnterCallback(win, &WindowSystem::onMouseEnterWindow);
ContextHelper::setKeyCallback(win, &WindowSystem::onKeyInput);
ContextHelper::setCharCallback(win, &WindowSystem::onCharInput);
ContextHelper::setCursorPosCallback(win, &WindowSystem::onMouseMoved);
ContextHelper::setMouseButtonCallback(win, &WindowSystem::onMouseButton);
ContextHelper::setScrollCallback(win, &WindowSystem::onMouseScroll);
ContextHelper::makeContextCurrent(nullptr);
};
ecs::component_handle<window> handle(request.entityId);
if (!m_ecs->hasComponent<window>(request.entityId))
{
win.lock = new async::spinlock();
std::lock_guard wguard(*win.lock); // This is the only code that has access to win.lock right now, so there's no deadlock risk.
std::lock_guard cguard(m_creationLock);// Locking them both separately is faster than using a multilock.
m_windowComponents.insert(win, handle);
handle = m_ecs->createComponent<window>(request.entityId, win);
log::trace("created window: {}", request.name);
// Set all callbacks.
setCallbacks(win);
}
else
{
handle = m_ecs->getComponent<window>(request.entityId);
window oldWindow = handle.read();
std::scoped_lock wguard(*oldWindow.lock, m_creationLock);
m_windowComponents.erase(oldWindow);
ContextHelper::destroyWindow(oldWindow);
win.lock = oldWindow.lock;
handle.write(win);
m_windowComponents.insert(win, handle);
log::trace("replaced window: {}", request.name);
// Set all callbacks.
setCallbacks(win);
}
}
m_creationRequests.clear();
}
void WindowSystem::fullscreenWindows()
{
OPTICK_EVENT();
if (m_exit) // If the engine is exiting then we can't change any windows.
return;
std::lock_guard guard(m_fullscreenRequestLock);
for (auto& request : m_fullscreenRequests)
{
if (!m_ecs->validateEntity(request.entityId))
{
log::warn("Fullscreen toggle denied, entity {} doesn't exist.", request.entityId);
continue;
}
auto handle = m_ecs->getComponent<window>(request.entityId);
if (!handle)
{
log::warn("Fullscreen toggle denied, entity {} doesn't have a window.", request.entityId);
continue;
}
window win = handle.read();
std::lock_guard wguard(*win.lock);
if (win.m_isFullscreen)
{
GLFWmonitor* monitor = ContextHelper::getPrimaryMonitor();
const GLFWvidmode* mode = ContextHelper::getVideoMode(monitor);
ContextHelper::setWindowMonitor(win, nullptr, request.position, request.size, mode->refreshRate);
win.m_size = request.size;
log::trace("Set window {} to windowed.", request.entityId);
}
else
{
GLFWmonitor* monitor = ContextHelper::getCurrentMonitor(win);
const GLFWvidmode* mode = ContextHelper::getVideoMode(monitor);
ContextHelper::setWindowMonitor(win, monitor, { 0 ,0 }, math::ivec2(mode->width, mode->height), mode->refreshRate);
win.m_size = math::ivec2(mode->width, mode->height);
ContextHelper::makeContextCurrent(win);
ContextHelper::swapInterval(win.m_swapInterval);
ContextHelper::makeContextCurrent(nullptr);
log::trace("Set window {} to fullscreen.", request.entityId);
}
win.m_isFullscreen = !win.m_isFullscreen;
handle.write(win);
}
m_fullscreenRequests.clear();
}
void WindowSystem::updateWindowIcons()
{
OPTICK_EVENT();
if (m_exit) // If the engine is exiting then we can't change any windows.
return;
std::lock_guard guard(m_iconRequestLock);
for (auto& request : m_iconRequests)
{
if (!m_ecs->validateEntity(request.entityId))
{
log::warn("Icon change denied, entity {} doesn't exist.", request.entityId);
continue;
}
auto handle = m_ecs->getComponent<window>(request.entityId);
if (!handle)
{
log::warn("Icon change denied, entity {} doesn't have a window.", request.entityId);
continue;
}
auto [lock, image] = request.icon.get_raw_image();
async::readonly_guard guard(lock);
if (image.components != image_components::rgba || image.format != channel_format::eight_bit)
{
log::warn("Icon change denied, image {} has the wrong format. The needed format is 4 channels with 8 bits per channel.", request.icon.id);
continue;
}
GLFWimage icon{ image.size.x, image.size.y, image.get_raw_data<byte>() };
ContextHelper::setWindowIcon(handle.read(), 1, &icon);
}
m_iconRequests.clear();
}
void WindowSystem::refreshWindows(time::time_span<fast_time> deltaTime)
{
OPTICK_EVENT();
if (!ContextHelper::initialized())
return;
std::lock_guard guard(m_creationLock);
m_windowQuery.queryEntities();
for (auto entity : m_windowQuery)
{
window win = entity.get_component_handle<window>().read();
{
context_guard guard(win);
if (!guard.contextIsValid())
continue;
ContextHelper::swapBuffers(win);
}
}
}
void WindowSystem::handleWindowEvents(time::time_span<fast_time> deltaTime)
{
OPTICK_EVENT();
createWindows();
updateWindowIcons();
fullscreenWindows();
if (!ContextHelper::initialized())
return;
ContextHelper::pollEvents();
ContextHelper::updateWindowFocus();
}
}