chore: removed some unused stuff
This commit is contained in:
parent
a88b2ed08b
commit
6b4a87dd76
6 changed files with 810 additions and 851 deletions
|
|
@ -123,8 +123,6 @@ if(WIN32)
|
|||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
SOURCES
|
||||
platform_windows.cpp
|
||||
DEPENDENCIES
|
||||
ecs
|
||||
app
|
||||
|
|
@ -147,8 +145,6 @@ elseif(UNIX)
|
|||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
SOURCES
|
||||
platform_linux.cpp
|
||||
DEPENDENCIES
|
||||
ecs
|
||||
app
|
||||
|
|
@ -163,7 +159,6 @@ elseif(UNIX)
|
|||
time
|
||||
TESTS
|
||||
system.test.cpp
|
||||
platform_linux.test.cpp
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import test.test;
|
||||
import test.expects;
|
||||
import surface.system;
|
||||
import surface.events;
|
||||
import surface.requests;
|
||||
import ecs.registry;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
import logger;
|
||||
import math.vec2;
|
||||
import app.system;
|
||||
import std;
|
||||
|
||||
using ::lt::surface::SurfaceComponent;
|
||||
using ::lt::surface::System;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_ne;
|
||||
using ::lt::test::expect_not_nullptr;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::Suite;
|
||||
using ::std::ignore;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
Suite raii = "platform_linux_raii"_suite = [] {
|
||||
auto registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
std::ignore = System { registry };
|
||||
};
|
||||
|
|
@ -1,527 +0,0 @@
|
|||
module;
|
||||
#include <Windows.h>
|
||||
module surface.system;
|
||||
import surface.constants;
|
||||
import debug.assertions;
|
||||
import memory.reference;
|
||||
import surface.requests;
|
||||
import surface.events;
|
||||
import logger;
|
||||
import ecs.registry;
|
||||
import ecs.entity;
|
||||
import time;
|
||||
import std;
|
||||
|
||||
namespace lt::surface {
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component);
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT;
|
||||
|
||||
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||
{
|
||||
debug::ensure(m_registry, "Failed to initialize surface system: null registry");
|
||||
|
||||
debug::ensure(
|
||||
m_registry->view<SurfaceComponent>().get_size() == 0,
|
||||
"Failed to initialize surface system: registry has surface component(s)"
|
||||
);
|
||||
|
||||
m_registry->connect_on_destruct<SurfaceComponent>(
|
||||
[this](ecs::Registry ®istry, ecs::EntityId entity) {
|
||||
on_surface_destruct(registry, entity);
|
||||
}
|
||||
);
|
||||
|
||||
auto window_class = WNDCLASS {
|
||||
.lpfnWndProc = native_window_proc,
|
||||
.hInstance = GetModuleHandle(nullptr),
|
||||
.lpszClassName = constants::class_name,
|
||||
};
|
||||
RegisterClass(&window_class);
|
||||
}
|
||||
|
||||
System::~System()
|
||||
{
|
||||
if (!m_registry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO(Light): make registry.remove not invalidate iterators
|
||||
auto entities_to_remove = std::vector<ecs::EntityId> {};
|
||||
for (auto &[entity, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
entities_to_remove.emplace_back(entity);
|
||||
}
|
||||
|
||||
for (auto entity : entities_to_remove)
|
||||
{
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
m_registry->disconnect_on_construct<SurfaceComponent>();
|
||||
m_registry->disconnect_on_destruct<SurfaceComponent>();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Uncaught exception in surface::~System:");
|
||||
log::error("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
||||
void System::on_unregister()
|
||||
{
|
||||
}
|
||||
|
||||
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
|
||||
try
|
||||
{
|
||||
auto &component = m_registry->add<SurfaceComponent>(entity, info);
|
||||
auto &surface = m_registry->get<SurfaceComponent>(entity);
|
||||
const auto &resolution = surface.get_resolution();
|
||||
const auto &position = surface.get_position();
|
||||
ensure_component_sanity(surface);
|
||||
|
||||
surface.m_native_data.window = CreateWindowEx(
|
||||
0,
|
||||
constants::class_name,
|
||||
info.title.data(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetModuleHandle(nullptr),
|
||||
nullptr
|
||||
);
|
||||
debug::ensure(surface.m_native_data.window, "Failed to create Windows surface component");
|
||||
|
||||
ShowWindow(surface.m_native_data.window, SW_NORMAL);
|
||||
|
||||
// TODO(Light): refactor "environment" into standalone module
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
// auto *display_env = std::getenv("DISPLAY");
|
||||
// debug::ensure(display_env != nullptr, "DISPLAY env var not found!");
|
||||
//
|
||||
// auto *display = XOpenDisplay(display_env);
|
||||
// debug::ensure(display, "Failed to open XDisplay with DISPLAY: {}", display_env);
|
||||
//
|
||||
// auto root_window = XDefaultRootWindow(display);
|
||||
//
|
||||
// auto border_width = 0;
|
||||
// auto depth = std::int32_t { CopyFromParent };
|
||||
// auto window_class = CopyFromParent;
|
||||
// auto *visual = (Visual *)CopyFromParent;
|
||||
//
|
||||
// auto attribute_value_mask = CWBackPixel | CWEventMask;
|
||||
// auto attributes = XSetWindowAttributes {
|
||||
// .background_pixel = 0xffafe9af,
|
||||
// .event_mask = all_events_mask,
|
||||
// };
|
||||
//
|
||||
// typedef struct Hints
|
||||
// {
|
||||
// unsigned long flags;
|
||||
// unsigned long functions;
|
||||
// unsigned long decorations;
|
||||
// long inputMode;
|
||||
// unsigned long status;
|
||||
// } Hints;
|
||||
//
|
||||
// auto main_window = XCreateWindow(
|
||||
// display,
|
||||
// root_window,
|
||||
// position.x,
|
||||
// position.y,
|
||||
// resolution.x,
|
||||
// resolution.y,
|
||||
// border_width,
|
||||
// depth,
|
||||
// window_class,
|
||||
// visual,
|
||||
// attribute_value_mask,
|
||||
// &attributes
|
||||
// );
|
||||
// surface.m_native_data.display = display;
|
||||
// surface.m_native_data.window = main_window;
|
||||
//
|
||||
// surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||
// XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1);
|
||||
//
|
||||
// // code to remove decoration
|
||||
// auto hints = std::array<const unsigned char, 5> { 2, 0, 0, 0, 0 };
|
||||
// const auto motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False);
|
||||
//
|
||||
// XChangeProperty(
|
||||
// display,
|
||||
// surface.m_native_data.window,
|
||||
// motif_hints,
|
||||
// motif_hints,
|
||||
// 32,
|
||||
// PropModeReplace,
|
||||
// hints.data(),
|
||||
// 5
|
||||
// );
|
||||
//
|
||||
// XMapWindow(display, main_window);
|
||||
// XStoreName(display, main_window, surface.m_title.c_str());
|
||||
// XFlush(display);
|
||||
//
|
||||
// if (!surface.is_visible())
|
||||
// {
|
||||
// XUnmapWindow(display, main_window);
|
||||
// }
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Exception thrown when on_constructing surface component");
|
||||
log::error("\tentity: {}", std::uint32_t { entity });
|
||||
log::error("\twhat: {}", exp.what());
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
void System::on_surface_destruct(ecs::Registry ®istry, ecs::EntityId entity)
|
||||
{
|
||||
auto *window = registry.get<SurfaceComponent>(entity).get_native_data().window;
|
||||
if (!window)
|
||||
{
|
||||
log::warn("Surface component destroyed with null window handle");
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyWindow(window);
|
||||
}
|
||||
|
||||
void System::handle_events(SurfaceComponent &surface)
|
||||
{
|
||||
auto &queue = surface.m_event_queue;
|
||||
queue.clear();
|
||||
|
||||
auto message = MSG {};
|
||||
while (PeekMessage(&message, 0, {}, {}, PM_REMOVE))
|
||||
{
|
||||
switch (message.message)
|
||||
{
|
||||
}
|
||||
|
||||
log::debug("Window message type: {}", std::uint32_t { message.message });
|
||||
}
|
||||
|
||||
// auto event = XEvent {};
|
||||
// auto &[display, window, wm_delete_message] = surface.m_native_data;
|
||||
//
|
||||
// XFlush(display);
|
||||
// while (XEventsQueued(display, QueuedAlready) != 0)
|
||||
// {
|
||||
// XNextEvent(surface.m_native_data.display, &event);
|
||||
//
|
||||
// switch (event.type)
|
||||
// {
|
||||
// case KeyPress:
|
||||
// {
|
||||
// queue.emplace_back<KeyPressedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case KeyRelease:
|
||||
// {
|
||||
// queue.emplace_back<KeyReleasedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case ButtonPress:
|
||||
// {
|
||||
// queue.emplace_back<ButtonPressedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case ButtonRelease:
|
||||
// {
|
||||
// queue.emplace_back<ButtonReleasedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case FocusIn:
|
||||
// {
|
||||
// queue.emplace_back<GainFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case FocusOut:
|
||||
// {
|
||||
// queue.emplace_back<LostFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case ClientMessage:
|
||||
// {
|
||||
// if (event.xclient.data.l[0] == wm_delete_message)
|
||||
// {
|
||||
// queue.emplace_back<ClosedEvent>({});
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case MotionNotify:
|
||||
// {
|
||||
// queue.emplace_back<MouseMovedEvent>(MouseMovedEvent {
|
||||
// static_cast<float>(event.xmotion.x),
|
||||
// static_cast<float>(event.xmotion.y),
|
||||
// });
|
||||
// break;
|
||||
// }
|
||||
// case ConfigureNotify:
|
||||
// {
|
||||
// const auto [prev_width, prev_height] = surface.get_resolution();
|
||||
// const auto new_width = event.xconfigure.width;
|
||||
// const auto new_height = event.xconfigure.height;
|
||||
// if (prev_width != new_width || prev_height != new_height)
|
||||
// {
|
||||
// surface.m_resolution.x = new_width;
|
||||
// surface.m_resolution.y = new_height;
|
||||
// queue.emplace_back<ResizedEvent>(ResizedEvent {
|
||||
// static_cast<std::uint32_t>(new_width),
|
||||
// static_cast<std::uint32_t>(new_height),
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// const auto [prev_x, prev_y] = surface.get_position();
|
||||
// const auto new_x = event.xconfigure.x;
|
||||
// const auto new_y = event.xconfigure.y;
|
||||
// if (prev_x != new_x || prev_y != new_y)
|
||||
// {
|
||||
// surface.m_position.x = new_x;
|
||||
// surface.m_position.y = new_y;
|
||||
// queue.emplace_back<MovedEvent>(MovedEvent {
|
||||
// new_x,
|
||||
// new_y,
|
||||
// });
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// default: break; /* pass */
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void System::handle_requests(SurfaceComponent &surface)
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](const ModifyTitleRequest &request) { modify_title(surface, request); },
|
||||
[&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); },
|
||||
[&](const ModifyPositionRequest &request) { modify_position(surface, request); },
|
||||
[&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); },
|
||||
[&](const auto &) { log::error("Unknown surface request"); },
|
||||
};
|
||||
|
||||
for (const auto &request : surface.peek_requests())
|
||||
{
|
||||
std::visit(visitor, request);
|
||||
}
|
||||
|
||||
surface.m_requests.clear();
|
||||
}
|
||||
|
||||
void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request)
|
||||
{
|
||||
surface.m_title = request.title;
|
||||
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// XStoreName(display, window, request.title.c_str());
|
||||
}
|
||||
|
||||
void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request)
|
||||
{
|
||||
// surface.m_resolution = request.resolution;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[width, height] = request.resolution;
|
||||
// // XResizeWindow(display, window, width, height);
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
//
|
||||
// // request a new window size from the X server
|
||||
// XResizeWindow(
|
||||
// display,
|
||||
// window,
|
||||
// static_cast<std::uint32_t>(width),
|
||||
// static_cast<std::uint32_t>(height)
|
||||
// );
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// // The documentation for XResizeWindow includes this important note:
|
||||
// //
|
||||
// // If the override-redirect flag of the window is False and some
|
||||
// // other client has selected SubstructureRedirectMask on the parent,
|
||||
// // the X server generates a ConfigureRequest event, and no further
|
||||
// // processing is performed.
|
||||
// //
|
||||
// // What this means, essentially, is that if this window is a top-level
|
||||
// // window, then it's the window manager (the "other client") that is
|
||||
// // responsible for changing this window's size. So when we call
|
||||
// // XResizeWindow() on a top-level window, then instead of resizing
|
||||
// // the window immediately, the X server informs the window manager,
|
||||
// // and then the window manager sets our new size (usually it will be
|
||||
// // the size we asked for). We receive a ConfigureNotify event when
|
||||
// // our new size has been set.
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XResizeWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request)
|
||||
{
|
||||
// surface.m_position = request.position;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[x, y] = request.position;
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
// XMoveWindow(display, window, static_cast<int>(x), static_cast<int>(y));
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XMoveWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request)
|
||||
{
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// surface.m_visible = request.visible;
|
||||
|
||||
// if (request.visible)
|
||||
// {
|
||||
// XMapWindow(display, window);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// XUnmapWindow(display, window);
|
||||
// }
|
||||
}
|
||||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
for (auto &[id, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
handle_requests(surface);
|
||||
handle_events(surface);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
m_last_tick_result = app::TickResult {
|
||||
.info = tick,
|
||||
.duration = now - tick.start_time,
|
||||
.end_time = now,
|
||||
};
|
||||
}
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component)
|
||||
{
|
||||
auto [width, height] = component.get_resolution();
|
||||
|
||||
debug::ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width);
|
||||
|
||||
debug::ensure(
|
||||
height != 0u,
|
||||
"Received bad values for surface component: height({}) == 0",
|
||||
height
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
width < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: width({}) > max_dimension({})",
|
||||
width,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
height < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: height({}) > max_dimension({})",
|
||||
height,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
component.get_title().size() < SurfaceComponent::max_title_length,
|
||||
"Received bad values for surface component: title.size({}) > max_title_length({})",
|
||||
component.get_title().size(),
|
||||
SurfaceComponent::max_title_length
|
||||
);
|
||||
}
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_DESTROY:
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
} // namespace lt::surface
|
||||
|
|
@ -106,7 +106,6 @@ namespace lt::surface {
|
|||
void handle_shell_ping(void *data, xdg_wm_base *shell, std::uint32_t serial)
|
||||
{
|
||||
std::ignore = data;
|
||||
|
||||
xdg_wm_base_pong(shell, serial);
|
||||
}
|
||||
const auto shell_listener = xdg_wm_base_listener {
|
||||
|
|
@ -264,6 +263,532 @@ void System::tick(app::TickInfo tick)
|
|||
#endif
|
||||
|
||||
#ifdef LIGHT_PLATFORM_WINDOWS
|
||||
|
||||
module;
|
||||
#include <Windows.h>
|
||||
module surface.system;
|
||||
import surface.constants;
|
||||
import debug.assertions;
|
||||
import memory.reference;
|
||||
import surface.requests;
|
||||
import surface.events;
|
||||
import logger;
|
||||
import ecs.registry;
|
||||
import ecs.entity;
|
||||
import time;
|
||||
import std;
|
||||
|
||||
namespace lt::surface {
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component);
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT;
|
||||
|
||||
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||
{
|
||||
debug::ensure(m_registry, "Failed to initialize surface system: null registry");
|
||||
|
||||
debug::ensure(
|
||||
m_registry->view<SurfaceComponent>().get_size() == 0,
|
||||
"Failed to initialize surface system: registry has surface component(s)"
|
||||
);
|
||||
|
||||
m_registry->connect_on_destruct<SurfaceComponent>(
|
||||
[this](ecs::Registry ®istry, ecs::EntityId entity) {
|
||||
on_surface_destruct(registry, entity);
|
||||
}
|
||||
);
|
||||
|
||||
auto window_class = WNDCLASS {
|
||||
.lpfnWndProc = native_window_proc,
|
||||
.hInstance = GetModuleHandle(nullptr),
|
||||
.lpszClassName = constants::class_name,
|
||||
};
|
||||
RegisterClass(&window_class);
|
||||
}
|
||||
|
||||
System::~System()
|
||||
{
|
||||
if (!m_registry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO(Light): make registry.remove not invalidate iterators
|
||||
auto entities_to_remove = std::vector<ecs::EntityId> {};
|
||||
for (auto &[entity, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
entities_to_remove.emplace_back(entity);
|
||||
}
|
||||
|
||||
for (auto entity : entities_to_remove)
|
||||
{
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
m_registry->disconnect_on_construct<SurfaceComponent>();
|
||||
m_registry->disconnect_on_destruct<SurfaceComponent>();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Uncaught exception in surface::~System:");
|
||||
log::error("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
||||
void System::on_unregister()
|
||||
{
|
||||
}
|
||||
|
||||
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
|
||||
try
|
||||
{
|
||||
auto &component = m_registry->add<SurfaceComponent>(entity, info);
|
||||
auto &surface = m_registry->get<SurfaceComponent>(entity);
|
||||
const auto &resolution = surface.get_resolution();
|
||||
const auto &position = surface.get_position();
|
||||
ensure_component_sanity(surface);
|
||||
|
||||
surface.m_native_data.window = CreateWindowEx(
|
||||
0,
|
||||
constants::class_name,
|
||||
info.title.data(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetModuleHandle(nullptr),
|
||||
nullptr
|
||||
);
|
||||
debug::ensure(surface.m_native_data.window, "Failed to create Windows surface component");
|
||||
|
||||
ShowWindow(surface.m_native_data.window, SW_NORMAL);
|
||||
|
||||
// TODO(Light): refactor "environment" into standalone module
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
// auto *display_env = std::getenv("DISPLAY");
|
||||
// debug::ensure(display_env != nullptr, "DISPLAY env var not found!");
|
||||
//
|
||||
// auto *display = XOpenDisplay(display_env);
|
||||
// debug::ensure(display, "Failed to open XDisplay with DISPLAY: {}", display_env);
|
||||
//
|
||||
// auto root_window = XDefaultRootWindow(display);
|
||||
//
|
||||
// auto border_width = 0;
|
||||
// auto depth = std::int32_t { CopyFromParent };
|
||||
// auto window_class = CopyFromParent;
|
||||
// auto *visual = (Visual *)CopyFromParent;
|
||||
//
|
||||
// auto attribute_value_mask = CWBackPixel | CWEventMask;
|
||||
// auto attributes = XSetWindowAttributes {
|
||||
// .background_pixel = 0xffafe9af,
|
||||
// .event_mask = all_events_mask,
|
||||
// };
|
||||
//
|
||||
// typedef struct Hints
|
||||
// {
|
||||
// unsigned long flags;
|
||||
// unsigned long functions;
|
||||
// unsigned long decorations;
|
||||
// long inputMode;
|
||||
// unsigned long status;
|
||||
// } Hints;
|
||||
//
|
||||
// auto main_window = XCreateWindow(
|
||||
// display,
|
||||
// root_window,
|
||||
// position.x,
|
||||
// position.y,
|
||||
// resolution.x,
|
||||
// resolution.y,
|
||||
// border_width,
|
||||
// depth,
|
||||
// window_class,
|
||||
// visual,
|
||||
// attribute_value_mask,
|
||||
// &attributes
|
||||
// );
|
||||
// surface.m_native_data.display = display;
|
||||
// surface.m_native_data.window = main_window;
|
||||
//
|
||||
// surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||
// XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1);
|
||||
//
|
||||
// // code to remove decoration
|
||||
// auto hints = std::array<const unsigned char, 5> { 2, 0, 0, 0, 0 };
|
||||
// const auto motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False);
|
||||
//
|
||||
// XChangeProperty(
|
||||
// display,
|
||||
// surface.m_native_data.window,
|
||||
// motif_hints,
|
||||
// motif_hints,
|
||||
// 32,
|
||||
// PropModeReplace,
|
||||
// hints.data(),
|
||||
// 5
|
||||
// );
|
||||
//
|
||||
// XMapWindow(display, main_window);
|
||||
// XStoreName(display, main_window, surface.m_title.c_str());
|
||||
// XFlush(display);
|
||||
//
|
||||
// if (!surface.is_visible())
|
||||
// {
|
||||
// XUnmapWindow(display, main_window);
|
||||
// }
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Exception thrown when on_constructing surface component");
|
||||
log::error("\tentity: {}", std::uint32_t { entity });
|
||||
log::error("\twhat: {}", exp.what());
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
void System::on_surface_destruct(ecs::Registry ®istry, ecs::EntityId entity)
|
||||
{
|
||||
auto *window = registry.get<SurfaceComponent>(entity).get_native_data().window;
|
||||
if (!window)
|
||||
{
|
||||
log::warn("Surface component destroyed with null window handle");
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyWindow(window);
|
||||
}
|
||||
|
||||
void System::handle_events(SurfaceComponent &surface)
|
||||
{
|
||||
auto &queue = surface.m_event_queue;
|
||||
queue.clear();
|
||||
|
||||
auto message = MSG {};
|
||||
while (PeekMessage(&message, 0, {}, {}, PM_REMOVE))
|
||||
{
|
||||
switch (message.message) {}
|
||||
|
||||
log::debug("Window message type: {}", std::uint32_t { message.message });
|
||||
}
|
||||
|
||||
// auto event = XEvent {};
|
||||
// auto &[display, window, wm_delete_message] = surface.m_native_data;
|
||||
//
|
||||
// XFlush(display);
|
||||
// while (XEventsQueued(display, QueuedAlready) != 0)
|
||||
// {
|
||||
// XNextEvent(surface.m_native_data.display, &event);
|
||||
//
|
||||
// switch (event.type)
|
||||
// {
|
||||
// case KeyPress:
|
||||
// {
|
||||
// queue.emplace_back<KeyPressedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case KeyRelease:
|
||||
// {
|
||||
// queue.emplace_back<KeyReleasedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case ButtonPress:
|
||||
// {
|
||||
// queue.emplace_back<ButtonPressedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case ButtonRelease:
|
||||
// {
|
||||
// queue.emplace_back<ButtonReleasedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case FocusIn:
|
||||
// {
|
||||
// queue.emplace_back<GainFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case FocusOut:
|
||||
// {
|
||||
// queue.emplace_back<LostFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case ClientMessage:
|
||||
// {
|
||||
// if (event.xclient.data.l[0] == wm_delete_message)
|
||||
// {
|
||||
// queue.emplace_back<ClosedEvent>({});
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case MotionNotify:
|
||||
// {
|
||||
// queue.emplace_back<MouseMovedEvent>(MouseMovedEvent {
|
||||
// static_cast<float>(event.xmotion.x),
|
||||
// static_cast<float>(event.xmotion.y),
|
||||
// });
|
||||
// break;
|
||||
// }
|
||||
// case ConfigureNotify:
|
||||
// {
|
||||
// const auto [prev_width, prev_height] = surface.get_resolution();
|
||||
// const auto new_width = event.xconfigure.width;
|
||||
// const auto new_height = event.xconfigure.height;
|
||||
// if (prev_width != new_width || prev_height != new_height)
|
||||
// {
|
||||
// surface.m_resolution.x = new_width;
|
||||
// surface.m_resolution.y = new_height;
|
||||
// queue.emplace_back<ResizedEvent>(ResizedEvent {
|
||||
// static_cast<std::uint32_t>(new_width),
|
||||
// static_cast<std::uint32_t>(new_height),
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// const auto [prev_x, prev_y] = surface.get_position();
|
||||
// const auto new_x = event.xconfigure.x;
|
||||
// const auto new_y = event.xconfigure.y;
|
||||
// if (prev_x != new_x || prev_y != new_y)
|
||||
// {
|
||||
// surface.m_position.x = new_x;
|
||||
// surface.m_position.y = new_y;
|
||||
// queue.emplace_back<MovedEvent>(MovedEvent {
|
||||
// new_x,
|
||||
// new_y,
|
||||
// });
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// default: break; /* pass */
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void System::handle_requests(SurfaceComponent &surface)
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](const ModifyTitleRequest &request) { modify_title(surface, request); },
|
||||
[&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); },
|
||||
[&](const ModifyPositionRequest &request) { modify_position(surface, request); },
|
||||
[&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); },
|
||||
[&](const auto &) { log::error("Unknown surface request"); },
|
||||
};
|
||||
|
||||
for (const auto &request : surface.peek_requests())
|
||||
{
|
||||
std::visit(visitor, request);
|
||||
}
|
||||
|
||||
surface.m_requests.clear();
|
||||
}
|
||||
|
||||
void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request)
|
||||
{
|
||||
surface.m_title = request.title;
|
||||
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// XStoreName(display, window, request.title.c_str());
|
||||
}
|
||||
|
||||
void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request)
|
||||
{
|
||||
// surface.m_resolution = request.resolution;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[width, height] = request.resolution;
|
||||
// // XResizeWindow(display, window, width, height);
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
//
|
||||
// // request a new window size from the X server
|
||||
// XResizeWindow(
|
||||
// display,
|
||||
// window,
|
||||
// static_cast<std::uint32_t>(width),
|
||||
// static_cast<std::uint32_t>(height)
|
||||
// );
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// // The documentation for XResizeWindow includes this important note:
|
||||
// //
|
||||
// // If the override-redirect flag of the window is False and some
|
||||
// // other client has selected SubstructureRedirectMask on the parent,
|
||||
// // the X server generates a ConfigureRequest event, and no further
|
||||
// // processing is performed.
|
||||
// //
|
||||
// // What this means, essentially, is that if this window is a top-level
|
||||
// // window, then it's the window manager (the "other client") that is
|
||||
// // responsible for changing this window's size. So when we call
|
||||
// // XResizeWindow() on a top-level window, then instead of resizing
|
||||
// // the window immediately, the X server informs the window manager,
|
||||
// // and then the window manager sets our new size (usually it will be
|
||||
// // the size we asked for). We receive a ConfigureNotify event when
|
||||
// // our new size has been set.
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XResizeWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request)
|
||||
{
|
||||
// surface.m_position = request.position;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[x, y] = request.position;
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
// XMoveWindow(display, window, static_cast<int>(x), static_cast<int>(y));
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XMoveWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request)
|
||||
{
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// surface.m_visible = request.visible;
|
||||
|
||||
// if (request.visible)
|
||||
// {
|
||||
// XMapWindow(display, window);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// XUnmapWindow(display, window);
|
||||
// }
|
||||
}
|
||||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
for (auto &[id, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
handle_requests(surface);
|
||||
handle_events(surface);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
m_last_tick_result = app::TickResult {
|
||||
.info = tick,
|
||||
.duration = now - tick.start_time,
|
||||
.end_time = now,
|
||||
};
|
||||
}
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component)
|
||||
{
|
||||
auto [width, height] = component.get_resolution();
|
||||
|
||||
debug::ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width);
|
||||
|
||||
debug::ensure(
|
||||
height != 0u,
|
||||
"Received bad values for surface component: height({}) == 0",
|
||||
height
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
width < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: width({}) > max_dimension({})",
|
||||
width,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
height < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: height({}) > max_dimension({})",
|
||||
height,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
component.get_title().size() < SurfaceComponent::max_title_length,
|
||||
"Received bad values for surface component: title.size({}) > max_title_length({})",
|
||||
component.get_title().size(),
|
||||
SurfaceComponent::max_title_length
|
||||
);
|
||||
}
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_DESTROY:
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
} // namespace lt::surface
|
||||
#endif
|
||||
|
||||
} // namespace lt::surface
|
||||
|
|
|
|||
|
|
@ -1,290 +1,284 @@
|
|||
// Suite raii = "raii"_suite = [] {
|
||||
// Case { "happy path won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// ignore = System { fixture.registry() };
|
||||
// };
|
||||
//
|
||||
// import test.test;
|
||||
// import test.expects;
|
||||
// import surface.system;
|
||||
// import surface.events;
|
||||
// import surface.requests;
|
||||
// import ecs.registry;
|
||||
// import memory.scope;
|
||||
// import memory.reference;
|
||||
// import logger;
|
||||
// import math.vec2;
|
||||
// import app.system;
|
||||
// import std;
|
||||
//
|
||||
// using ::lt::surface::SurfaceComponent;
|
||||
// using ::lt::surface::System;
|
||||
// using ::lt::test::Case;
|
||||
// using ::lt::test::expect_eq;
|
||||
// using ::lt::test::expect_ne;
|
||||
// using ::lt::test::expect_not_nullptr;
|
||||
// using ::lt::test::expect_throw;
|
||||
// using ::lt::test::Suite;
|
||||
// using ::std::ignore;
|
||||
// using ::lt::test::operator""_suite;
|
||||
//
|
||||
// [[nodiscard]] auto tick_info() -> lt::app::TickInfo
|
||||
// {
|
||||
// return {
|
||||
// .delta_time = std::chrono::milliseconds { 16 },
|
||||
// .budget = std::chrono::milliseconds { 10 },
|
||||
// .start_time = std::chrono::steady_clock::now(),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// constexpr auto title = "TestWindow";
|
||||
// constexpr auto width = 800u;
|
||||
// constexpr auto height = 600u;
|
||||
// constexpr auto vsync = true;
|
||||
// constexpr auto visible = false;
|
||||
//
|
||||
// template<class... Ts>
|
||||
// struct overloads: Ts...
|
||||
// {
|
||||
// using Ts::operator()...;
|
||||
// };
|
||||
//
|
||||
// class Fixture
|
||||
// {
|
||||
// public:
|
||||
// [[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
|
||||
// {
|
||||
// return m_registry;
|
||||
// }
|
||||
//
|
||||
// auto create_component(
|
||||
// SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo {
|
||||
// .title = title,
|
||||
// .resolution = { width, height },
|
||||
// .vsync = vsync,
|
||||
// .visible = visible,
|
||||
// }
|
||||
// ) -> std::optional<SurfaceComponent *>
|
||||
// {
|
||||
// auto entity = m_registry->create_entity();
|
||||
// m_system.create_surface_component(entity, info);
|
||||
//
|
||||
// return &m_registry->get<SurfaceComponent>(entity);
|
||||
// }
|
||||
//
|
||||
// void check_values(SurfaceComponent *component)
|
||||
// {
|
||||
// #ifdef LIGHT_PLATFORM_LINUX
|
||||
// expect_not_nullptr(component->get_native_data().display);
|
||||
// expect_ne(component->get_native_data().window, 0);
|
||||
// #endif
|
||||
//
|
||||
// expect_eq(component->get_resolution().x, width);
|
||||
// expect_eq(component->get_resolution().y, height);
|
||||
// expect_eq(component->get_title(), title);
|
||||
// expect_eq(component->is_vsync(), vsync);
|
||||
// expect_eq(component->is_visible(), visible);
|
||||
// }
|
||||
//
|
||||
// private:
|
||||
// lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
//
|
||||
// System m_system { m_registry };
|
||||
// };
|
||||
//
|
||||
//
|
||||
// Suite raii = "raii"_suite = [] {
|
||||
// Case { "happy path won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// ignore = System { fixture.registry() };
|
||||
// };
|
||||
//
|
||||
// Case { "many won't freeze/throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// for (auto idx : std::views::iota(0, 250))
|
||||
// {
|
||||
// ignore = System { fixture.registry() };
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// Case { "unhappy path throws" } = [] {
|
||||
// expect_throw([] { ignore = System { {} }; });
|
||||
// };
|
||||
//
|
||||
// Case { "post construct has correct state" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
//
|
||||
// Case { "post destruct has correct state" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = lt::memory::create_scope<System>(fixture.registry());
|
||||
//
|
||||
// fixture.create_component();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
//
|
||||
// system.reset();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// Suite system_events = "system_events"_suite = [] {
|
||||
// Case { "on_register won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
//
|
||||
// system.on_register();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
//
|
||||
// Case { "on_unregister won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
//
|
||||
// system.on_register();
|
||||
// system.on_unregister();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// Suite registry_events = "registry_events"_suite = [] {
|
||||
// Case { "on_construct<SurfaceComponent> initializes component" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
//
|
||||
// const auto &component = fixture.create_component();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
// fixture.check_values(*component);
|
||||
// };
|
||||
//
|
||||
// Case { "unhappy on_construct<SurfaceComponent> throws" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
//
|
||||
// expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
|
||||
//
|
||||
// expect_throw([&] { fixture.create_component({ .resolution = { 0, height } }); });
|
||||
//
|
||||
// expect_throw([&] {
|
||||
// fixture.create_component(
|
||||
// { .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } }
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// expect_throw([&] {
|
||||
// fixture.create_component(
|
||||
// { .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } }
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// auto big_str = std::string {};
|
||||
// big_str.resize(SurfaceComponent::max_title_length + 1);
|
||||
// expect_throw([&] {
|
||||
// fixture.create_component({ .title = big_str, .resolution = { width, height } });
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
//
|
||||
// expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
//
|
||||
// Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = lt::memory::create_scope<System>(fixture.registry());
|
||||
//
|
||||
// const auto &component = fixture.create_component();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
// fixture.check_values(*component);
|
||||
//
|
||||
// system.reset();
|
||||
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// Suite tick = "tick"_suite = [] {
|
||||
// Case { "ticking on empty registry won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// System { fixture.registry() }.tick(tick_info());
|
||||
// };
|
||||
//
|
||||
// Case { "ticking on non-empty registry won't throw" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
//
|
||||
// fixture.create_component();
|
||||
// system.tick(tick_info());
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// Suite tick_handles_events = "tick_handles_events"_suite = [] {
|
||||
// Case { "ticking clears previous tick's events" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
// auto &surface = **fixture.create_component();
|
||||
//
|
||||
// // flush window-creation events
|
||||
// system.tick(tick_info());
|
||||
// expect_eq(surface.peek_events().size(), 0);
|
||||
//
|
||||
// surface.push_event(lt::surface::MovedEvent({}, {}));
|
||||
// expect_eq(surface.peek_events().size(), 1);
|
||||
//
|
||||
// surface.push_event(lt::surface::ButtonPressedEvent({}));
|
||||
// expect_eq(surface.peek_events().size(), 2);
|
||||
//
|
||||
// system.tick(tick_info());
|
||||
// expect_eq(surface.peek_events().size(), 0);
|
||||
// };
|
||||
// };
|
||||
//
|
||||
// Suite tick_handles_requests = "tick_handles_requests"_suite = [] {
|
||||
// Case { "ticking clears requests" } = [] {
|
||||
// auto fixture = Fixture {};
|
||||
// auto system = System { fixture.registry() };
|
||||
// auto &surface = **fixture.create_component();
|
||||
//
|
||||
// constexpr auto title = "ABC";
|
||||
// constexpr auto position = lt::math::ivec2 { 50, 50 };
|
||||
// constexpr auto resolution = lt::math::uvec2 { 50, 50 };
|
||||
//
|
||||
// expect_eq(surface.peek_requests().size(), 0);
|
||||
//
|
||||
// surface.push_request(lt::surface::ModifyVisibilityRequest(true));
|
||||
// expect_eq(surface.peek_requests().size(), 1);
|
||||
// system.tick(tick_info());
|
||||
// expect_eq(surface.peek_requests().size(), 0);
|
||||
//
|
||||
// surface.push_request(lt::surface::ModifyTitleRequest(title));
|
||||
// expect_eq(surface.peek_requests().size(), 1);
|
||||
//
|
||||
// surface.push_request(lt::surface::ModifyResolutionRequest(resolution));
|
||||
// surface.push_request(lt::surface::ModifyPositionRequest(position));
|
||||
// expect_eq(surface.peek_requests().size(), 1 + 2);
|
||||
//
|
||||
// surface.push_request(lt::surface::ModifyVisibilityRequest(false));
|
||||
// surface.push_request(lt::surface::ModifyVisibilityRequest(true));
|
||||
// surface.push_request(lt::surface::ModifyVisibilityRequest(false));
|
||||
// expect_eq(surface.peek_requests().size(), 1 + 2 + 3);
|
||||
//
|
||||
// system.tick(tick_info());
|
||||
// expect_eq(surface.peek_requests().size(), 0);
|
||||
//
|
||||
// expect_eq(surface.get_title(), title);
|
||||
// expect_eq(surface.get_position(), position);
|
||||
// expect_eq(surface.get_resolution(), resolution);
|
||||
//
|
||||
// lt::log::debug("EVENT COUNT: {}", surface.peek_events().size());
|
||||
// for (const auto &event : surface.peek_events())
|
||||
// {
|
||||
// const auto visitor = overloads {
|
||||
// [&](auto event) { lt::log::debug("event: {}", event.to_string()); },
|
||||
// };
|
||||
//
|
||||
// std::visit(visitor, event);
|
||||
// }
|
||||
// };
|
||||
// };
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import surface.system;
|
||||
import surface.events;
|
||||
import surface.requests;
|
||||
import ecs.registry;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
import logger;
|
||||
import math.vec2;
|
||||
import app.system;
|
||||
import std;
|
||||
|
||||
using ::lt::surface::SurfaceComponent;
|
||||
using ::lt::surface::System;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_ne;
|
||||
using ::lt::test::expect_not_nullptr;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::Suite;
|
||||
using ::std::ignore;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
[[nodiscard]] auto tick_info() -> lt::app::TickInfo
|
||||
{
|
||||
return {
|
||||
.delta_time = std::chrono::milliseconds { 16 },
|
||||
.budget = std::chrono::milliseconds { 10 },
|
||||
.start_time = std::chrono::steady_clock::now(),
|
||||
};
|
||||
}
|
||||
|
||||
constexpr auto title = "TestWindow";
|
||||
constexpr auto width = 800u;
|
||||
constexpr auto height = 600u;
|
||||
constexpr auto vsync = true;
|
||||
constexpr auto visible = false;
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
|
||||
{
|
||||
return m_registry;
|
||||
}
|
||||
|
||||
auto create_component(
|
||||
SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo {
|
||||
.title = title,
|
||||
.resolution = { width, height },
|
||||
.vsync = vsync,
|
||||
.visible = visible,
|
||||
}
|
||||
) -> std::optional<SurfaceComponent *>
|
||||
{
|
||||
auto entity = m_registry->create_entity();
|
||||
m_system.create_surface_component(entity, info);
|
||||
|
||||
return &m_registry->get<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
void check_values(SurfaceComponent *component)
|
||||
{
|
||||
#ifdef LIGHT_PLATFORM_LINUX
|
||||
expect_not_nullptr(component->get_native_data().display);
|
||||
expect_not_nullptr(component->get_native_data().surface);
|
||||
#endif
|
||||
|
||||
expect_eq(component->get_resolution().x, width);
|
||||
expect_eq(component->get_resolution().y, height);
|
||||
expect_eq(component->get_title(), title);
|
||||
expect_eq(component->is_vsync(), vsync);
|
||||
expect_eq(component->is_visible(), visible);
|
||||
}
|
||||
|
||||
private:
|
||||
lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
|
||||
System m_system { m_registry };
|
||||
};
|
||||
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
ignore = System { fixture.registry() };
|
||||
};
|
||||
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
for (auto idx : std::views::iota(0, 250))
|
||||
{
|
||||
ignore = System { fixture.registry() };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
|
||||
Case { "post destruct has correct state" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = lt::memory::create_scope<System>(fixture.registry());
|
||||
|
||||
fixture.create_component();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
|
||||
system.reset();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite system_events = "system_events"_suite = [] {
|
||||
Case { "on_register won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
system.on_register();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
|
||||
Case { "on_unregister won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
system.on_register();
|
||||
system.on_unregister();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite registry_events = "registry_events"_suite = [] {
|
||||
Case { "on_construct<SurfaceComponent> initializes component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
|
||||
const auto &component = fixture.create_component();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
fixture.check_values(*component);
|
||||
};
|
||||
|
||||
Case { "unhappy on_construct<SurfaceComponent> throws" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
|
||||
|
||||
expect_throw([&] { fixture.create_component({ .resolution = { 0, height } }); });
|
||||
|
||||
expect_throw([&] {
|
||||
fixture.create_component(
|
||||
{ .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } }
|
||||
);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
fixture.create_component(
|
||||
{ .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } }
|
||||
);
|
||||
});
|
||||
|
||||
auto big_str = std::string {};
|
||||
big_str.resize(SurfaceComponent::max_title_length + 1);
|
||||
expect_throw([&] {
|
||||
fixture.create_component({ .title = big_str, .resolution = { width, height } });
|
||||
});
|
||||
};
|
||||
|
||||
Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
|
||||
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = lt::memory::create_scope<System>(fixture.registry());
|
||||
|
||||
const auto &component = fixture.create_component();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
|
||||
fixture.check_values(*component);
|
||||
|
||||
system.reset();
|
||||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite tick = "tick"_suite = [] {
|
||||
Case { "ticking on empty registry won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
System { fixture.registry() }.tick(tick_info());
|
||||
};
|
||||
|
||||
Case { "ticking on non-empty registry won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
fixture.create_component();
|
||||
system.tick(tick_info());
|
||||
};
|
||||
};
|
||||
|
||||
Suite tick_handles_events = "tick_handles_events"_suite = [] {
|
||||
Case { "ticking clears previous tick's events" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
auto &surface = **fixture.create_component();
|
||||
|
||||
// flush window-creation events
|
||||
system.tick(tick_info());
|
||||
expect_eq(surface.peek_events().size(), 0);
|
||||
|
||||
surface.push_event(lt::surface::MovedEvent({}, {}));
|
||||
expect_eq(surface.peek_events().size(), 1);
|
||||
|
||||
surface.push_event(lt::surface::ButtonPressedEvent({}));
|
||||
expect_eq(surface.peek_events().size(), 2);
|
||||
|
||||
system.tick(tick_info());
|
||||
expect_eq(surface.peek_events().size(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite tick_handles_requests = "tick_handles_requests"_suite = [] {
|
||||
Case { "ticking clears requests" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
auto &surface = **fixture.create_component();
|
||||
|
||||
constexpr auto title = "ABC";
|
||||
constexpr auto position = lt::math::ivec2 { 50, 50 };
|
||||
constexpr auto resolution = lt::math::uvec2 { 50, 50 };
|
||||
|
||||
expect_eq(surface.peek_requests().size(), 0);
|
||||
|
||||
surface.push_request(lt::surface::ModifyVisibilityRequest(true));
|
||||
expect_eq(surface.peek_requests().size(), 1);
|
||||
system.tick(tick_info());
|
||||
expect_eq(surface.peek_requests().size(), 0);
|
||||
|
||||
surface.push_request(lt::surface::ModifyTitleRequest(title));
|
||||
expect_eq(surface.peek_requests().size(), 1);
|
||||
|
||||
surface.push_request(lt::surface::ModifyResolutionRequest(resolution));
|
||||
surface.push_request(lt::surface::ModifyPositionRequest(position));
|
||||
expect_eq(surface.peek_requests().size(), 1 + 2);
|
||||
|
||||
surface.push_request(lt::surface::ModifyVisibilityRequest(false));
|
||||
surface.push_request(lt::surface::ModifyVisibilityRequest(true));
|
||||
surface.push_request(lt::surface::ModifyVisibilityRequest(false));
|
||||
expect_eq(surface.peek_requests().size(), 1 + 2 + 3);
|
||||
|
||||
system.tick(tick_info());
|
||||
expect_eq(surface.peek_requests().size(), 0);
|
||||
|
||||
expect_eq(surface.get_title(), title);
|
||||
expect_eq(surface.get_position(), position);
|
||||
expect_eq(surface.get_resolution(), resolution);
|
||||
|
||||
lt::log::debug("EVENT COUNT: {}", surface.peek_events().size());
|
||||
for (const auto &event : surface.peek_events())
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](auto event) { lt::log::debug("event: {}", event.to_string()); },
|
||||
};
|
||||
|
||||
std::visit(visitor, event);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue