Compare commits

...

2 commits

Author SHA1 Message Date
39824de3a5
wip: wayland input
Some checks reported errors
continuous-integration/drone/push Build was killed
2026-01-04 13:14:55 +03:30
6b4a87dd76
chore: removed some unused stuff 2026-01-02 14:48:42 +03:30
10 changed files with 1082 additions and 866 deletions

View file

@ -123,8 +123,6 @@ if(WIN32)
requests.cppm requests.cppm
events.cppm events.cppm
components.cppm components.cppm
SOURCES
platform_windows.cpp
DEPENDENCIES DEPENDENCIES
ecs ecs
app app
@ -147,8 +145,6 @@ elseif(UNIX)
requests.cppm requests.cppm
events.cppm events.cppm
components.cppm components.cppm
SOURCES
platform_linux.cpp
DEPENDENCIES DEPENDENCIES
ecs ecs
app app
@ -163,7 +159,6 @@ elseif(UNIX)
time time
TESTS TESTS
system.test.cpp system.test.cpp
platform_linux.test.cpp
) )
@ -235,12 +230,12 @@ add_module(
PRIVATE_DEPENDENCIES PRIVATE_DEPENDENCIES
surface surface
TESTS TESTS
_tests/buffer.cpp # _tests/buffer.cpp
_tests/debugger.cpp # _tests/debugger.cpp
_tests/device.cpp # _tests/device.cpp
_tests/pass.cpp # _tests/pass.cpp
_tests/renderer.cpp # _tests/renderer.cpp
_tests/surface.cpp # _tests/surface.cpp
_tests/system.cpp _tests/system.cpp
TEST_INTERFACES TEST_INTERFACES
_tests/utils.cppm _tests/utils.cppm

View file

@ -39,7 +39,6 @@ public:
return *this; return *this;
} }
log::debug("Nulling 0x{:x}", (std::size_t)other.m_value);
m_value = other.m_value; m_value = other.m_value;
other.m_value = null_value; other.m_value = null_value;

View file

@ -1,82 +1,111 @@
import time;
import renderer.frontend; import renderer.frontend;
import renderer.test_utils; import renderer.test_utils;
struct SurfaceContext struct SurfaceContext
{ {
lt::surface::System system; lt::surface::System system;
lt::ecs::Entity entity; lt::ecs::Entity entity;
}; };
struct RendererContext struct RendererContext
{ {
lt::memory::Ref<lt::ecs::Registry> registry; lt::memory::Ref<lt::ecs::Registry> registry;
lt::renderer::System system; lt::renderer::System system;
}; };
Suite raii = "system_raii"_suite = [] { Suite raii = "system_raii"_suite = [] {
Case { "happy path won't throw" } = [] { Case { "sandbox" } = [] {
ignore = Fixture_RendererSystem {};
};
Case { "happy path has no errors" } = [] {
auto fixture = Fixture_RendererSystem {}; auto fixture = Fixture_RendererSystem {};
expect_false(fixture.has_any_messages_of(lt::renderer::IDebugger::MessageSeverity::error)); auto &surface_system = fixture.surface_system();
expect_false( auto &renderer_system = fixture.renderer_system();
fixture.has_any_messages_of(lt::renderer::IDebugger::MessageSeverity::warning)
); auto timer = lt::time::Timer {};
lt::log::trace("Ticking for 3 seconds...");
while (timer.elapsed_time() < std::chrono::seconds { 3 })
{
surface_system.tick({});
renderer_system.tick({});
}
lt::log::trace("Three seconds passed, quitting...");
}; };
Case { "unhappy path throws" } = [] { // Case { "happy path won't throw" } = [] {
auto fixture = Fixture_SurfaceSystem {}; // ignore = Fixture_RendererSystem {};
auto empty_entity = lt::ecs::Entity { fixture.registry(), //
fixture.registry()->create_entity() }; //
auto info = fixture.renderer_system_create_info(); // auto timer = lt::time::Timer {};
// lt::log::trace("Ticking for 3 seconds...");
expect_throw([=] mutable { // while (timer.elapsed_time() < std::chrono::seconds { 3 })
info.registry = nullptr; // {
ignore = lt::renderer::System { info }; // system.tick({});
}); // }
//
expect_throw([=] mutable { // lt::log::trace("Three seconds passed, quitting...");
info.surface_entity = lt::ecs::Entity({}, {}); // };
ignore = lt::renderer::System { info }; //
}); // Case { "happy path has no errors" } = [] {
// auto fixture = Fixture_RendererSystem {};
expect_throw([=] mutable { // expect_false(fixture.has_any_messages_of(lt::renderer::IDebugger::MessageSeverity::error));
info.config.target_api = lt::renderer::Api::none; // expect_false(
ignore = lt::renderer::System { info }; // fixture.has_any_messages_of(lt::renderer::IDebugger::MessageSeverity::warning)
}); // );
// };
// unsupported Apis //
expect_throw([=] mutable { // Case { "unhappy path throws" } = [] {
info.config.target_api = lt::renderer::Api::direct_x; // auto fixture = Fixture_SurfaceSystem {};
ignore = lt::renderer::System { info }; // auto empty_entity = lt::ecs::Entity { fixture.registry(),
}); // fixture.registry()->create_entity() };
// auto info = fixture.renderer_system_create_info();
expect_throw([=] mutable { //
info.config.target_api = lt::renderer::Api::metal; // expect_throw([=] mutable {
ignore = lt::renderer::System { info }; // info.registry = nullptr;
}); // ignore = lt::renderer::System { info };
// });
expect_throw([=] mutable { //
constexpr auto limit = lt::renderer::System::frames_in_flight_upper_limit; // expect_throw([=] mutable {
info.config.max_frames_in_flight = limit + 1u; // info.surface_entity = lt::ecs::Entity({}, {});
ignore = lt::renderer::System { info }; // ignore = lt::renderer::System { info };
}); // });
//
expect_throw([=] mutable { // expect_throw([=] mutable {
constexpr auto limit = lt::renderer::System::frames_in_flight_lower_limit; // info.config.target_api = lt::renderer::Api::none;
info.config.max_frames_in_flight = limit - 1u; // ignore = lt::renderer::System { info };
ignore = lt::renderer::System { info }; // });
}); //
// // unsupported Apis
expect_throw([=] mutable { // expect_throw([=] mutable {
info.debug_callback_info = lt::renderer::IDebugger::CreateInfo {}; // info.config.target_api = lt::renderer::Api::direct_x;
ignore = lt::renderer::System { info }; // ignore = lt::renderer::System { info };
}); // });
//
// Make sure the base info is not at fault for unhappiness. // expect_throw([=] mutable {
ignore = lt::renderer::System { info }; // info.config.target_api = lt::renderer::Api::metal;
}; // ignore = lt::renderer::System { info };
// });
//
// expect_throw([=] mutable {
// constexpr auto limit = lt::renderer::System::frames_in_flight_upper_limit;
// info.config.max_frames_in_flight = limit + 1u;
// ignore = lt::renderer::System { info };
// });
//
// expect_throw([=] mutable {
// constexpr auto limit = lt::renderer::System::frames_in_flight_lower_limit;
// info.config.max_frames_in_flight = limit - 1u;
// ignore = lt::renderer::System { info };
// });
//
// expect_throw([=] mutable {
// info.debug_callback_info = lt::renderer::IDebugger::CreateInfo {};
// ignore = lt::renderer::System { info };
// });
//
// // Make sure the base info is not at fault for unhappiness.
// ignore = lt::renderer::System { info };
// };
}; };

View file

@ -3207,19 +3207,7 @@ Surface::Surface(const Instance &instance, const CreateInfo &info)
.surface = info.surface, .surface = info.surface,
}; };
log::debug(
"Display proxy's version: {}",
wl_proxy_get_version(std::bit_cast<wl_proxy *>(info.display))
);
log::debug(
"Surface proxy's version: {}",
wl_proxy_get_version(std::bit_cast<wl_proxy *>(info.surface))
);
vkc(api::create_wayland_surface_khr(instance.get_vk_handle(), &vk_info, nullptr, &m_surface)); vkc(api::create_wayland_surface_khr(instance.get_vk_handle(), &vk_info, nullptr, &m_surface));
log::debug("Wayland surface vulkan handle id is: {}", (size_t)m_surface);
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
const auto vk_info = VkWin32SurfaceCreateInfoKHR { const auto vk_info = VkWin32SurfaceCreateInfoKHR {
@ -3868,17 +3856,13 @@ Device::Device(const Gpu &gpu, CreateInfo info)
} }
vkc(api::create_device(gpu.m_physical_device, &vk_info, nullptr, &m_device)); vkc(api::create_device(gpu.m_physical_device, &vk_info, nullptr, &m_device));
log::debug("Created device: 0x{:x}", (size_t)m_device, (size_t)m_device);
} }
Device::~Device() Device::~Device()
{ {
if (m_device) if (m_device)
{ {
log::debug("Destroying device {:x}...", (size_t)m_device);
api::destroy_device(m_device, nullptr); api::destroy_device(m_device, nullptr);
log::debug("...Destroyed device");
std::cout << "D" << std::endl;
} }
} }
@ -4587,9 +4571,6 @@ Swapchain::Swapchain(Device &device, Surface &surface, CreateInfo info)
: m_device(device.m_device.get()) : m_device(device.m_device.get())
, m_swapchain() , m_swapchain()
{ {
log::debug("Wayland surface vulkan handle id is now: 0x{:x}", (size_t)surface.m_surface);
log::debug("Got device for swapchain: 0x{:x}", (size_t)m_device);
auto vk_info = VkSwapchainCreateInfoKHR { auto vk_info = VkSwapchainCreateInfoKHR {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = surface.m_surface, .surface = surface.m_surface,
@ -4608,36 +4589,20 @@ Swapchain::Swapchain(Device &device, Surface &surface, CreateInfo info)
.clipped = VK_TRUE, .clipped = VK_TRUE,
.oldSwapchain = nullptr, .oldSwapchain = nullptr,
}; };
log::debug("Creating swapchain: 0x{:x}", (size_t)m_swapchain);
vkc(api::create_swapchain_khr(m_device, &vk_info, nullptr, &m_swapchain)); vkc(api::create_swapchain_khr(m_device, &vk_info, nullptr, &m_swapchain));
log::debug("Created swapchain: 0x{:x}", (size_t)m_swapchain);
if (info.name.empty()) if (info.name.empty())
{ {
info.name = "<unnamed>"; info.name = "<unnamed>";
} }
device.name(*this, "{}", info.name); device.name(*this, "{}", info.name);
log::debug("Still got device for swapchain: 0x{:x}", (size_t)m_device);
} }
Swapchain::~Swapchain() Swapchain::~Swapchain()
{ {
if (m_device) if (m_device)
{ {
log::debug("Destroyig swapchain...");
log::debug("device: 0x{:x}", (size_t)m_device);
log::debug("swapchain: 0x{:x}", (size_t)m_swapchain);
log::debug("vkDestroySwapchainKHR: 0x{:x}", (size_t)api::destroy_swapchain_khr);
api::destroy_swapchain_khr(m_device, m_swapchain, nullptr); api::destroy_swapchain_khr(m_device, m_swapchain, nullptr);
log::debug("...Destroyed swapchain");
}
else
{
log::debug(
"Skipped destruction of Swapchain due to nulled device: 0{:x}",
(size_t)m_device
);
} }
} }
@ -4694,9 +4659,10 @@ Buffer::Buffer(Device &device, CreateInfo info): m_device(device.m_device.get())
Buffer::~Buffer() Buffer::~Buffer()
{ {
std::cout << "B" << std::endl; if (m_device)
api::destroy_buffer(m_device, m_buffer, nullptr); {
std::cout << "C" << std::endl; api::destroy_buffer(m_device, m_buffer, nullptr);
}
} }
[[nodiscard]] auto Buffer::get_memory_requirements() const -> MemoryRequirements [[nodiscard]] auto Buffer::get_memory_requirements() const -> MemoryRequirements
@ -5104,8 +5070,10 @@ Messenger::Messenger(Instance &instance, CreateInfo info): m_instance(instance.g
Messenger::~Messenger() Messenger::~Messenger()
{ {
api::destroy_debug_messenger(m_instance, m_messenger, nullptr); if (m_instance)
std::cout << "C1" << std::endl; {
api::destroy_debug_messenger(m_instance, m_messenger, nullptr);
}
} }
[[nodiscard]] [[nodiscard]]

View file

@ -193,7 +193,7 @@ Renderer::Renderer(
frame_fence.reset(); frame_fence.reset();
map_buffers(frame_idx); map_buffers(frame_idx);
submit_scene(); // submit_scene();
record_cmd(cmd, image_idx); record_cmd(cmd, image_idx);
auto &submit_semaphore = m_submit_semaphores[image_idx]; auto &submit_semaphore = m_submit_semaphores[image_idx];
@ -250,35 +250,35 @@ void Renderer::record_cmd(vk::CommandBuffer &cmd, std::uint32_t image_idx)
m_staging_buffer.unmap(); m_staging_buffer.unmap();
if (m_current_sprite_idx) // if (m_current_sprite_idx)
{ // {
cmd.copy( // cmd.copy(
{ // {
.src_buffer = &m_staging_buffer.vk(), // .src_buffer = &m_staging_buffer.vk(),
.dst_buffer = &m_vertex_buffer.vk(), // .dst_buffer = &m_vertex_buffer.vk(),
.src_offset = m_staging_offset, // .src_offset = m_staging_offset,
.dst_offset = m_staging_offset, // .dst_offset = m_staging_offset,
.size = m_current_sprite_idx * sizeof(components::Sprite::Vertex), // .size = m_current_sprite_idx * sizeof(components::Sprite::Vertex),
} // }
); // );
} // }
cmd.push_constants( // cmd.push_constants(
{ // {
.layout = &m_pass->get_pipeline_layout(), // .layout = &m_pass->get_pipeline_layout(),
.shader_stages = vk::ShaderStageFlags::vertex_bit, // .shader_stages = vk::ShaderStageFlags::vertex_bit,
.offset = 0u, // .offset = 0u,
.size = sizeof(FrameConstants), // .size = sizeof(FrameConstants),
.data = &m_frame_constants, // .data = &m_frame_constants,
} // }
); // );
//
cmd.bind_descriptor_set( // cmd.bind_descriptor_set(
m_global_set, // m_global_set,
vk::Pipeline::BindPoint::graphics, // vk::Pipeline::BindPoint::graphics,
m_pass->get_pipeline_layout(), // m_pass->get_pipeline_layout(),
0 // 0
); // );
using AccessFlagBits = vk::CommandBuffer::ImageBarrierInfo::AccessFlagBits; using AccessFlagBits = vk::CommandBuffer::ImageBarrierInfo::AccessFlagBits;
cmd.image_barrier( cmd.image_barrier(
@ -310,25 +310,25 @@ void Renderer::record_cmd(vk::CommandBuffer &cmd, std::uint32_t image_idx)
} }
} }
); );
cmd.bind_pipeline(m_pass->get_pipeline(), vk::Pipeline::BindPoint::graphics); // cmd.bind_pipeline(m_pass->get_pipeline(), vk::Pipeline::BindPoint::graphics);
cmd.set_viewport( // cmd.set_viewport(
{ // {
.origin = {}, // .origin = {},
.extent = { static_cast<float>(m_resolution.x), static_cast<float>(m_resolution.y) }, // .extent = { static_cast<float>(m_resolution.x), static_cast<float>(m_resolution.y) },
.min_depth = 0.0f, // .min_depth = 0.0f,
.max_depth = 1.0f, // .max_depth = 1.0f,
} // }
); // );
cmd.set_scissor({ .offset = {}, .extent = m_resolution }); // cmd.set_scissor({ .offset = {}, .extent = m_resolution });
cmd.draw( // cmd.draw(
{ // {
.vertex_count = static_cast<std::uint32_t>(m_current_sprite_idx), // .vertex_count = static_cast<std::uint32_t>(m_current_sprite_idx),
.instance_count = 1u, // .instance_count = 1u,
.first_vertex = 0u, // .first_vertex = 0u,
.first_instance = 0u, // .first_instance = 0u,
} // }
); // );
//
cmd.end_rendering(); cmd.end_rendering();
cmd.image_barrier( cmd.image_barrier(
{ {

View file

@ -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 };
};

View file

@ -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 &registry, 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 &registry, 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

View file

@ -6,7 +6,6 @@ module;
#error "Unsupported platform" #error "Unsupported platform"
#endif #endif
import logger;
export module surface.system; export module surface.system;
export import :components; export import :components;
import debug.assertions; import debug.assertions;
@ -16,6 +15,7 @@ import math.vec2;
import surface.requests; import surface.requests;
import memory.reference; import memory.reference;
import memory.null_on_move; import memory.null_on_move;
import logger;
import std; import std;
export namespace lt::surface { export namespace lt::surface {
@ -50,13 +50,82 @@ public:
private: private:
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
static void handle_globals( static void wayland_registry_listener(
void *data, void *data,
wl_registry *registry, wl_registry *registry,
std::uint32_t name, std::uint32_t name,
const char *interface, const char *interface,
std::uint32_t version std::uint32_t version
); );
static void wayland_seat_capabilities_listener(
void *data,
wl_seat *seat,
std::uint32_t capabilities
);
static void wayland_pointer_leave_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
wl_surface *surface
);
static void wayland_pointer_enter_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
wl_surface *surface,
wl_fixed_t surface_x,
wl_fixed_t surface_y
);
static void wayland_pointer_motion_listener(
void *data,
wl_pointer *listener,
std::uint32_t time,
wl_fixed_t surface_x,
wl_fixed_t surface_y
);
static void wayland_pointer_button_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
std::uint32_t time,
std::uint32_t button,
std::uint32_t state
);
static void wayland_pointer_axis_listener(
void *data,
wl_pointer *pointer,
std::uint32_t time,
std::uint32_t axis,
wl_fixed_t value
);
static void wayland_pointer_axis_source_listener(
void *data,
wl_pointer *pointer,
std::uint32_t axis_source
);
static void wayland_pointer_axis_stop_listener(
void *data,
wl_pointer *pointer,
std::uint32_t time,
std::uint32_t axis_source
);
static void wayland_pointer_axis_discrete_listener(
void *data,
wl_pointer *pointer,
std::uint32_t axis,
std::int32_t discrete
);
static void wayland_pointer_frame_listener(void *data, wl_pointer *pointer);
#endif #endif
void on_surface_destruct(ecs::Registry &registry, ecs::EntityId entity); void on_surface_destruct(ecs::Registry &registry, ecs::EntityId entity);
@ -81,18 +150,32 @@ private:
memory::Ref<ecs::Registry> m_registry; memory::Ref<ecs::Registry> m_registry;
app::TickResult m_last_tick_result; app::TickResult m_last_tick_result {};
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
memory::NullOnMove<wl_display *> m_wl_display {}; memory::NullOnMove<wl_display *> m_wl_display {};
memory::NullOnMove<wl_registry *> m_wl_registry {}; wl_registry *m_wl_registry {};
wl_registry_listener m_wl_registry_listener {}; wl_registry_listener m_wl_registry_listener {};
memory::NullOnMove<wl_compositor *> m_wl_compositor {}; wl_seat_listener m_wl_seat_listener {};
wl_pointer_listener m_wl_pointer_listener {};
wl_compositor *m_wl_compositor {};
xdg_wm_base *m_shell = {};
wl_seat *m_wl_seat {};
wl_keyboard *m_wl_keyboard {};
wl_pointer *m_wl_pointer {};
wl_touch *m_wl_touch {}; // TODO(Light): Add touch support
memory::NullOnMove<xdg_wm_base *> m_shell = {};
#endif #endif
}; };
@ -106,7 +189,6 @@ namespace lt::surface {
void handle_shell_ping(void *data, xdg_wm_base *shell, std::uint32_t serial) void handle_shell_ping(void *data, xdg_wm_base *shell, std::uint32_t serial)
{ {
std::ignore = data; std::ignore = data;
xdg_wm_base_pong(shell, serial); xdg_wm_base_pong(shell, serial);
} }
const auto shell_listener = xdg_wm_base_listener { const auto shell_listener = xdg_wm_base_listener {
@ -142,7 +224,143 @@ const auto toplevel_listener = xdg_toplevel_listener {
.close = &handle_toplevel_close, .close = &handle_toplevel_close,
}; };
void System::handle_globals( void wayland_pointer_leave_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
wl_surface *surface
)
{
void *system = std::bit_cast<System *>(data);
}
/* static */ void System::wayland_seat_capabilities_listener(
void *data,
wl_seat *seat,
std::uint32_t capabilities
)
{
std::ignore = seat;
auto *system = std::bit_cast<System *>(data);
const auto have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
if (have_pointer && !system->m_wl_pointer)
{
system->m_wl_pointer = wl_seat_get_pointer(system->m_wl_seat);
wl_pointer_add_listener(system->m_wl_pointer, &system->m_wl_pointer_listener, system);
log::info(
"Added Wayland pointer (0x{:x})",
std::bit_cast<std::size_t>(system->m_wl_pointer)
);
}
else if (!have_pointer && system->m_wl_pointer)
{
wl_pointer_release(system->m_wl_pointer);
system->m_wl_pointer = nullptr;
log::info(
"Released Wayland pointer (0x{:x})",
std::bit_cast<std::size_t>(system->m_wl_pointer)
);
}
}
/* static */ void System::wayland_pointer_leave_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
wl_surface *surface
)
{
log::debug("Pointer leave...");
}
/* static */ void System::wayland_pointer_enter_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
wl_surface *surface,
wl_fixed_t surface_x,
wl_fixed_t surface_y
)
{
log::debug("Pointer enter...");
}
/* static */ void System::wayland_pointer_motion_listener(
void *data,
wl_pointer *listener,
std::uint32_t time,
wl_fixed_t surface_x,
wl_fixed_t surface_y
)
{
log::debug("Pointer motion: [{} - {}]", surface_x, surface_y);
}
/* static */ void System::wayland_pointer_button_listener(
void *data,
wl_pointer *pointer,
std::uint32_t serial,
std::uint32_t time,
std::uint32_t button,
std::uint32_t state
)
{
}
/* static */ void System::wayland_pointer_axis_listener(
void *data,
wl_pointer *pointer,
std::uint32_t time,
std::uint32_t axis,
wl_fixed_t value
)
{
}
/* static */ void System::wayland_pointer_axis_source_listener(
void *data,
wl_pointer *pointer,
std::uint32_t axis_source
)
{
}
/* static */ void System::wayland_pointer_axis_stop_listener(
void *data,
wl_pointer *pointer,
std::uint32_t time,
std::uint32_t axis_source
)
{
}
/* static */ void System::wayland_pointer_axis_discrete_listener(
void *data,
wl_pointer *pointer,
std::uint32_t axis,
std::int32_t discrete
)
{
}
/* static */ void System::wayland_pointer_frame_listener(void *data, wl_pointer *pointer)
{
log::debug("Pointer frame...");
}
void seat_name_listener(void *data, wl_seat *seat, const char *name)
{
std::ignore = data;
log::info("Wayland seat:");
log::info("\tname: {}", name);
log::info("\taddr: 0x{:x}", std::bit_cast<std::size_t>(seat));
}
void System::wayland_registry_listener(
void *data, void *data,
wl_registry *registry, wl_registry *registry,
std::uint32_t name, std::uint32_t name,
@ -151,6 +369,8 @@ void System::handle_globals(
) )
{ {
std::ignore = version;
auto *system = std::bit_cast<System *>(data); auto *system = std::bit_cast<System *>(data);
if (std::strcmp(interface, wl_compositor_interface.name) == 0) if (std::strcmp(interface, wl_compositor_interface.name) == 0)
@ -166,9 +386,18 @@ void System::handle_globals(
system->m_shell = std::bit_cast<xdg_wm_base *>( system->m_shell = std::bit_cast<xdg_wm_base *>(
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1) wl_registry_bind(registry, name, &xdg_wm_base_interface, 1)
); );
xdg_wm_base_add_listener(system->m_shell, &shell_listener, {}); xdg_wm_base_add_listener(system->m_shell, &shell_listener, system);
log::info("Bound successfuly to the xdg_wm_base global"); log::info("Bound successfuly to the xdg_wm_base global");
} }
if (std::strcmp(interface, wl_seat_interface.name) == 0)
{
system->m_wl_seat = std::bit_cast<wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, 7u)
);
wl_seat_add_listener(system->m_wl_seat, &system->m_wl_seat_listener, system);
log::info("Bound successfuly to the wl_seat_interface global");
}
} }
void registry_handle_global_remove(void *data, wl_registry *registry, std::uint32_t name) void registry_handle_global_remove(void *data, wl_registry *registry, std::uint32_t name)
@ -180,11 +409,30 @@ void registry_handle_global_remove(void *data, wl_registry *registry, std::uint3
System::System(memory::Ref<ecs::Registry> registry) System::System(memory::Ref<ecs::Registry> registry)
: m_wl_registry_listener( : m_wl_registry_listener(
{ {
.global = handle_globals, .global = wayland_registry_listener,
.global_remove = registry_handle_global_remove, .global_remove = registry_handle_global_remove,
} }
) )
, m_wl_seat_listener(
wl_seat_listener {
.capabilities = &wayland_seat_capabilities_listener,
.name = &seat_name_listener,
}
)
, m_registry(std::move(registry)) , m_registry(std::move(registry))
, m_wl_pointer_listener(
{
.enter = &wayland_pointer_enter_listener,
.leave = &wayland_pointer_leave_listener,
.motion = &wayland_pointer_motion_listener,
.button = &wayland_pointer_button_listener,
.axis = &wayland_pointer_axis_listener,
.frame = &wayland_pointer_frame_listener,
.axis_source = &wayland_pointer_axis_source_listener,
.axis_stop = &wayland_pointer_axis_stop_listener,
.axis_discrete = &wayland_pointer_axis_discrete_listener,
}
)
{ {
// NOLINTNEXTLINE // NOLINTNEXTLINE
m_wl_display = wl_display_connect({}); m_wl_display = wl_display_connect({});
@ -198,30 +446,30 @@ System::System(memory::Ref<ecs::Registry> registry)
wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this); wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this);
wl_display_roundtrip(m_wl_display); wl_display_roundtrip(m_wl_display);
// Wayland seat gets named after the second roundtrip....
// For reasons beyond my fragile comprehension :(
wl_display_roundtrip(m_wl_display);
debug::ensure(m_wl_compositor, "Failed to bind to the Wayland's compositor global"); debug::ensure(m_wl_compositor, "Failed to bind to the Wayland's compositor global");
debug::ensure(m_shell, "Failed to bind to the Wayland's XDG-shell global"); debug::ensure(m_shell, "Failed to bind to the Wayland's XDG-shell global");
} }
System::~System() System::~System()
{ {
if (m_wl_display) if (!m_wl_display)
{ {
log::debug("Closing Wayland display..."); return;
wl_display_disconnect(m_wl_display);
log::debug("Closed Wayland display");
}
else
{
log::debug("Wayland display nulled on move!");
} }
} }
void System::on_register() void System::on_register()
{ {
log::info("surface::System::on_register");
} }
void System::on_unregister() void System::on_unregister()
{ {
log::info("surface::System::on_unregister");
} }
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info) void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
@ -259,11 +507,538 @@ void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::Cr
void System::tick(app::TickInfo tick) void System::tick(app::TickInfo tick)
{ {
wl_display_roundtrip(m_wl_display);
} }
#endif #endif
#ifdef LIGHT_PLATFORM_WINDOWS #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 &registry, 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 &registry, 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 #endif
} // namespace lt::surface } // namespace lt::surface

View file

@ -1,134 +1,139 @@
// Suite raii = "raii"_suite = [] { import test.test;
// Case { "happy path won't throw" } = [] { import time;
// auto fixture = Fixture {}; import test.expects;
// ignore = System { fixture.registry() }; import surface.system;
// }; import surface.events;
// import surface.requests;
// import test.test; import ecs.registry;
// import test.expects; import memory.scope;
// import surface.system; import memory.reference;
// import surface.events; import logger;
// import surface.requests; import math.vec2;
// import ecs.registry; import app.system;
// import memory.scope; import std;
// import memory.reference;
// import logger; using ::lt::surface::SurfaceComponent;
// import math.vec2; using ::lt::surface::System;
// import app.system; using ::lt::test::Case;
// import std; using ::lt::test::expect_eq;
// using ::lt::test::expect_ne;
// using ::lt::surface::SurfaceComponent; using ::lt::test::expect_not_nullptr;
// using ::lt::surface::System; using ::lt::test::expect_throw;
// using ::lt::test::Case; using ::lt::test::Suite;
// using ::lt::test::expect_eq; using ::std::ignore;
// using ::lt::test::expect_ne; using ::lt::test::operator""_suite;
// using ::lt::test::expect_not_nullptr;
// using ::lt::test::expect_throw; [[nodiscard]] auto tick_info() -> lt::app::TickInfo
// using ::lt::test::Suite; {
// using ::std::ignore; return {
// using ::lt::test::operator""_suite; .delta_time = std::chrono::milliseconds { 16 },
// .budget = std::chrono::milliseconds { 10 },
// [[nodiscard]] auto tick_info() -> lt::app::TickInfo .start_time = std::chrono::steady_clock::now(),
// { };
// return { }
// .delta_time = std::chrono::milliseconds { 16 },
// .budget = std::chrono::milliseconds { 10 }, constexpr auto title = "TestWindow";
// .start_time = std::chrono::steady_clock::now(), constexpr auto width = 800u;
// }; constexpr auto height = 600u;
// } constexpr auto vsync = true;
// constexpr auto visible = false;
// constexpr auto title = "TestWindow";
// constexpr auto width = 800u; template<class... Ts>
// constexpr auto height = 600u; struct overloads: Ts...
// constexpr auto vsync = true; {
// constexpr auto visible = false; using Ts::operator()...;
// };
// template<class... Ts>
// struct overloads: Ts... class Fixture
// { {
// using Ts::operator()...; public:
// }; [[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
// {
// class Fixture return m_registry;
// { }
// public:
// [[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry> auto create_component(
// { SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo {
// return m_registry; .title = title,
// } .resolution = { width, height },
// .vsync = vsync,
// auto create_component( .visible = visible,
// SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo { }
// .title = title, ) -> std::optional<SurfaceComponent *>
// .resolution = { width, height }, {
// .vsync = vsync, auto entity = m_registry->create_entity();
// .visible = visible, m_system.create_surface_component(entity, info);
// }
// ) -> std::optional<SurfaceComponent *> return &m_registry->get<SurfaceComponent>(entity);
// { }
// auto entity = m_registry->create_entity();
// m_system.create_surface_component(entity, info); void check_values(SurfaceComponent *component)
// {
// return &m_registry->get<SurfaceComponent>(entity); #ifdef LIGHT_PLATFORM_LINUX
// } expect_not_nullptr(component->get_native_data().display);
// expect_not_nullptr(component->get_native_data().surface);
// void check_values(SurfaceComponent *component) #endif
// {
// #ifdef LIGHT_PLATFORM_LINUX expect_eq(component->get_resolution().x, width);
// expect_not_nullptr(component->get_native_data().display); expect_eq(component->get_resolution().y, height);
// expect_ne(component->get_native_data().window, 0); expect_eq(component->get_title(), title);
// #endif expect_eq(component->is_vsync(), vsync);
// expect_eq(component->is_visible(), visible);
// expect_eq(component->get_resolution().x, width); }
// expect_eq(component->get_resolution().y, height);
// expect_eq(component->get_title(), title); private:
// expect_eq(component->is_vsync(), vsync); lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
// expect_eq(component->is_visible(), visible);
// } System m_system { m_registry };
// };
// private:
// lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
// Suite raii = "raii"_suite = [] {
// System m_system { m_registry }; Case { "happy path won't throw" } = [] {
// }; auto fixture = Fixture {};
// auto system = System { fixture.registry() };
//
// Suite raii = "raii"_suite = [] { auto timer = lt::time::Timer {};
// Case { "happy path won't throw" } = [] { lt::log::trace("Ticking for 3 seconds...");
// auto fixture = Fixture {}; while (timer.elapsed_time() < std::chrono::seconds { 3 })
// ignore = System { fixture.registry() }; {
// }; system.tick({});
// }
// Case { "many won't freeze/throw" } = [] {
// auto fixture = Fixture {}; lt::log::trace("Three seconds passed, quitting...");
// for (auto idx : std::views::iota(0, 250)) };
// {
// ignore = System { fixture.registry() };
// } // Case { "many won't freeze/throw" } = [] {
// }; // auto fixture = Fixture {};
// // for (auto idx : std::views::iota(0, 250))
// Case { "unhappy path throws" } = [] { // {
// expect_throw([] { ignore = System { {} }; }); // ignore = System { fixture.registry() };
// }; // }
// // };
// Case { "post construct has correct state" } = [] { //
// auto fixture = Fixture {}; // Case { "unhappy path throws" } = [] {
// auto system = System { fixture.registry() }; // expect_throw([] { ignore = System { {} }; });
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); // };
// }; //
// // Case { "post construct has correct state" } = [] {
// Case { "post destruct has correct state" } = [] { // auto fixture = Fixture {};
// auto fixture = Fixture {}; // auto system = System { fixture.registry() };
// auto system = lt::memory::create_scope<System>(fixture.registry()); // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
// // };
// fixture.create_component(); //
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1); // Case { "post destruct has correct state" } = [] {
// // auto fixture = Fixture {};
// system.reset(); // auto system = lt::memory::create_scope<System>(fixture.registry());
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); //
// }; // 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 = [] { // Suite system_events = "system_events"_suite = [] {
// Case { "on_register won't throw" } = [] { // Case { "on_register won't throw" } = [] {
// auto fixture = Fixture {}; // auto fixture = Fixture {};