#include #include #include #include #include #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include #include #endif namespace { auto sdl_throw_current_error(const std::source_location location) { throw std::runtime_error{std::format( "error at {}:{}:{} {}", location.file_name(), location.line(), location.column(), SDL_GetError() )}; } auto sdl_check( int errc, const std::source_location location = std::source_location::current() ) { if (errc != 0) { sdl_throw_current_error(location); } } // make sure someone already owns passed sdl_obj, or memory will be leaked auto sdl_check( auto* sdl_obj, const std::source_location location = std::source_location::current() ) { if (sdl_obj == nullptr) { sdl_throw_current_error(location); } return sdl_obj; } template struct sdl_wrapper_t : std::unique_ptr { explicit sdl_wrapper_t(SDLT* p) : std::unique_ptr{p, deletef} { sdl_check(p); } operator SDLT*() { return std::unique_ptr::get(); } }; using window_t = sdl_wrapper_t; using surface_t = sdl_wrapper_t; using renderer_t = sdl_wrapper_t; auto operator+(SDL_Point a, SDL_Point b) { return SDL_Point{.x = a.x + b.x, .y = a.y + b.y}; } using clock = std::chrono::steady_clock; constexpr int width = 1024; constexpr int height = 512; constexpr auto steps_per_second = 10'000; template struct [[nodiscard("give this a name so SDL_Quit is called at the end" )]] Defer { Defer() = default; Defer(const Defer&) = delete; Defer(Defer&&) = delete; auto operator=(Defer&&) = delete; auto operator=(const Defer&) = delete; ~Defer() { EF(); } }; auto poll_events(bool& continu) { for (SDL_Event e; SDL_PollEvent(&e);) { if (e.type == SDL_QUIT) { continu = false; } } } struct Walker { SDL_Point pos; SDL_Color col; }; constexpr auto html2sdl_color(Uint32 rgb) { return SDL_Color{ .r = static_cast(rgb >> 16U), .g = static_cast((rgb >> 8U) & 0xffU), .b = static_cast(rgb & 0xffU), .a = SDL_ALPHA_OPAQUE }; } } // namespace template 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(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 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] = newpoint; } sdl_check(SDL_RenderDrawPoints( 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_RenderCopy(renderer, texture, nullptr, nullptr)); SDL_RenderPresent(renderer); } int main() try { sdl_check(SDL_Init(SDL_INIT_VIDEO)); Defer defer_SDL_Quit; window_t window{SDL_CreateWindow( "random walk", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0 // SDL_WINDOW_FULLSCREEN )}; renderer_t renderer{SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED) }; SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); { // dump renderer info SDL_RendererInfo renderer_info; SDL_GetRendererInfo(renderer, &renderer_info); std::println("Renderer name: {}", renderer_info.name); std::println("Texture formats: "); for (Uint32 i = 0; i < renderer_info.num_texture_formats; i++) { std::puts(SDL_GetPixelFormatName(renderer_info.texture_formats[i])); } } // no clue who owns this auto* texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_TARGET, width, height ); sdl_check(SDL_SetRenderTarget(renderer, texture)); sdl_check( SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE) ); sdl_check(SDL_RenderClear(renderer)); const auto middle = SDL_Point{.x = width / 2, .y = height / 2}; auto walkers = std::to_array( {/* {.pos = middle, .col = html2sdl_color(0x522258)}, {.pos = middle, .col = html2sdl_color(0x8C3061)}, {.pos = middle, .col = html2sdl_color(0xC63C51)}, {.pos = middle, .col = html2sdl_color(0xD95F59)} */ /* {.pos = middle, .col = html2sdl_color(0x402E7A)}, {.pos = middle, .col = html2sdl_color(0x4C3BCF)}, {.pos = middle, .col = html2sdl_color(0x4B70F5)}, {.pos = middle, .col = html2sdl_color(0x3DC2EC)} */ {.pos = middle, .col = html2sdl_color(0x000000)}, {.pos = middle, .col = html2sdl_color(0x150050)}, {.pos = middle, .col = html2sdl_color(0x3F0071)}, {.pos = middle, .col = html2sdl_color(0xFB2576)} } ); std::mt19937_64 rne(std::random_device{}()); std::uniform_int_distribution dist(0, 3); bool continu = true; const auto start_time = clock::now(); auto next_poll_events_time = start_time; auto next_frame_render_time = start_time; 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 ); std::puts("initialization complete"); #ifdef __EMSCRIPTEN__ // Receives a function to call and some user data to provide it. emscripten_set_main_loop_arg(mainloop, &loopdata, 0, 1); #else while (continu) { mainloop(&loopdata); std::this_thread::sleep_until(next_frame_render_time); next_frame_render_time += std::chrono::nanoseconds{16666666}; } #endif // leaking memory, nothing I can do } catch (const std::exception& e) { std::puts(e.what()); return EXIT_FAILURE; }