refactor emscripten loop function as lambda

took inspiration from
d9b758661f/examples/libs/emscripten/emscripten_mainloop_stub.h
This commit is contained in:
Jakob Hördt 2025-07-09 23:05:51 +02:00
parent 2c24a938c5
commit 552d11ef86

151
main.cpp
View file

@ -11,7 +11,6 @@
#include <random>
#include <source_location>
#include <stdexcept>
#include <thread>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
@ -92,72 +91,14 @@ constexpr auto html2sdl_color(Uint32 rgb) {
.a = SDL_ALPHA_OPAQUE
};
}
template <typename F>
auto loop_fn_emscripten(void* main_loop_body_ptr) {
(*static_cast<F*>(main_loop_body_ptr))();
}
} // namespace
template <typename Loopdata>
void mainloop(void* userData) {
constexpr SDL_Point directions[] = {
{.x = -1, .y = 0}, {.x = 1, .y = 0}, {.x = 0, .y = -1}, {.x = 0, .y = 1}
};
auto& [renderer, texture, walkers, rne, dist, start_time, total_steps, next_poll_events_time, next_frame_render_time, frame_start, frame_time, continu] =
*static_cast<Loopdata*>(userData);
// measure frame_time
const auto now = clock::now();
frame_time = now - frame_start;
frame_start = now;
// possibly poll events
if (now >= next_poll_events_time) {
next_poll_events_time = now + std::chrono::milliseconds{20};
poll_events(continu);
}
// simulate steps for passed time //
// roughly make a batch for every millisecond
constexpr auto batchsize =
std::clamp(steps_per_second / 1000 /*ms*/, 1, 1000);
const auto total_time = now - start_time;
const auto target_total_steps = total_time * steps_per_second /
clock::duration{std::chrono::seconds{1}};
for (; total_steps + batchsize <= target_total_steps;
total_steps += batchsize) {
sdl_check(SDL_SetRenderTarget(renderer, texture));
sdl_check(SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND));
for (auto& walker : walkers) {
sdl_check(SDL_SetRenderDrawColor(
renderer, walker.col.r, walker.col.g, walker.col.b, walker.col.a
));
std::array<SDL_FPoint, batchsize> point_batch;
for (auto step = 0z; step < batchsize; ++step) {
SDL_Point newpoint;
do {
newpoint = walker.pos + directions[dist(rne)];
} while (newpoint.x < 0 or newpoint.x >= width or
newpoint.y < 0 or newpoint.y >= height);
walker.pos = newpoint;
point_batch[step] = SDL_FPoint{
static_cast<float>(newpoint.x),
static_cast<float>(newpoint.y)
};
}
sdl_check(SDL_RenderPoints(
renderer, point_batch.data(), point_batch.size()
));
}
}
///// Render screen
sdl_check(SDL_SetRenderTarget(renderer, nullptr));
sdl_check(
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE)
);
SDL_RenderClear(renderer);
sdl_check(SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE));
sdl_check(SDL_RenderTexture(renderer, texture, nullptr, nullptr));
SDL_RenderPresent(renderer);
}
int main() try {
sdl_check(SDL_Init(SDL_INIT_VIDEO));
Defer<SDL_Quit> defer_SDL_Quit;
@ -170,10 +111,8 @@ int main() try {
sdl_check(window_.get());
auto* renderer = sdl_check(renderer_.get());
{
// dump renderer info
std::println("Renderer name: {}", SDL_GetRendererName(renderer));
}
// no clue who owns this
auto* texture = SDL_CreateTexture(
@ -213,18 +152,84 @@ int main() try {
auto frame_start = start_time;
auto total_steps = 0ll;
clock::duration frame_time;
auto loopdata = std::tie(
renderer, texture, walkers, rne, dist, start_time, total_steps,
next_poll_events_time, next_frame_render_time, frame_start, frame_time,
continu
auto main_loop_body = [&] {
constexpr SDL_Point directions[] = {
{.x = -1, .y = 0},
{.x = 1, .y = 0},
{.x = 0, .y = -1},
{.x = 0, .y = 1}
};
// measure frame_time
const auto now = clock::now();
frame_time = now - frame_start;
frame_start = now;
// possibly poll events
if (now >= next_poll_events_time) {
next_poll_events_time = now + std::chrono::milliseconds{20};
poll_events(continu);
}
// simulate steps for passed time //
// roughly make a batch for every millisecond
constexpr auto batchsize =
std::clamp(steps_per_second / 1000 /*ms*/, 1, 1000);
const auto total_time = now - start_time;
const auto target_total_steps =
total_time * steps_per_second /
clock::duration{std::chrono::seconds{1}};
for (; total_steps + batchsize <= target_total_steps;
total_steps += batchsize) {
sdl_check(SDL_SetRenderTarget(renderer, texture));
sdl_check(
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND)
);
for (auto& walker : walkers) {
sdl_check(SDL_SetRenderDrawColor(
renderer, walker.col.r, walker.col.g, walker.col.b,
walker.col.a
));
std::array<SDL_FPoint, batchsize> point_batch;
for (auto step = 0z; step < batchsize; ++step) {
SDL_Point newpoint;
do {
newpoint = walker.pos + directions[dist(rne)];
} while (newpoint.x < 0 or newpoint.x >= width or
newpoint.y < 0 or newpoint.y >= height);
walker.pos = newpoint;
point_batch[step] = SDL_FPoint{
static_cast<float>(newpoint.x),
static_cast<float>(newpoint.y)
};
}
sdl_check(SDL_RenderPoints(
renderer, point_batch.data(), point_batch.size()
));
}
}
///// Render screen
sdl_check(SDL_SetRenderTarget(renderer, nullptr));
sdl_check(
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE)
);
SDL_RenderClear(renderer);
sdl_check(SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE));
sdl_check(SDL_RenderTexture(renderer, texture, nullptr, nullptr));
SDL_RenderPresent(renderer);
};
std::puts("initialization complete");
#ifdef __EMSCRIPTEN__
// Receives a function to call and some user data to provide it.
emscripten_set_main_loop_arg(mainloop<decltype(loopdata)>, &loopdata, 0, 1);
emscripten_set_main_loop_arg(
loop_fn_emscripten<decltype(main_loop_body)>, &main_loop_body, 0, true
);
#else
while (continu) {
mainloop<decltype(loopdata)>(&loopdata);
main_loop_body();
std::this_thread::sleep_until(next_frame_render_time);
next_frame_render_time += std::chrono::nanoseconds{16666666};
}