@@ -33,6 +33,7 @@ void stbi_image_free(void *);
3333#include < deque>
3434#include < mutex>
3535#include < atomic>
36+ #include < queue>
3637
3738#include < climits>
3839#ifdef _WIN32
@@ -101,6 +102,8 @@ class Overlay {
101102 void shutdown ();
102103 void spawn_badge (int sprite, float x, float y);
103104 void spawn_badge (float x, float y);
105+ void enqueue_spawn (int sprite, float x, float y);
106+ void enqueue_spawn (float x, float y);
104107 void run (std::stop_token st);
105108 void stop ();
106109 void refresh_from_config (const app::Config &cfg);
@@ -122,6 +125,7 @@ class Overlay {
122125 void render ();
123126 void update_frame_interval ();
124127 void apply_pending_config ();
128+ void process_spawn_queue ();
125129 struct AtlasData {
126130 std::vector<Sprite> sprites;
127131 std::unordered_map<std::string, int > lookup;
@@ -134,6 +138,12 @@ class Overlay {
134138 static std::optional<std::filesystem::path>
135139 normalize_path (const std::optional<std::filesystem::path> &path);
136140
141+ struct SpawnRequest {
142+ std::optional<int > sprite;
143+ float x;
144+ float y;
145+ };
146+
137147 struct PendingConfig {
138148 std::string spawn_strategy;
139149 int badge_min_px = 60 ;
@@ -178,6 +188,8 @@ class Overlay {
178188 std::optional<PendingConfig> m_pending_config;
179189 std::atomic<bool > m_has_pending_config{false };
180190 std::mutex m_spawn_config_mutex;
191+ std::mutex m_spawn_queue_mutex;
192+ std::queue<SpawnRequest> m_spawn_queue;
181193};
182194
183195void Overlay::update_frame_interval () {
@@ -759,6 +771,16 @@ void Overlay::spawn_badge(int sprite, float x, float y) {
759771 spawn_badge_locked (sprite, x, y);
760772}
761773
774+ void Overlay::enqueue_spawn (float x, float y) {
775+ std::lock_guard<std::mutex> lock (m_spawn_queue_mutex);
776+ m_spawn_queue.push (SpawnRequest{std::nullopt , x, y});
777+ }
778+
779+ void Overlay::enqueue_spawn (int sprite, float x, float y) {
780+ std::lock_guard<std::mutex> lock (m_spawn_queue_mutex);
781+ m_spawn_queue.push (SpawnRequest{std::make_optional (sprite), x, y});
782+ }
783+
762784int Overlay::select_sprite_locked () {
763785 if (m_selector_indices.empty ()) {
764786 return 0 ;
@@ -827,6 +849,23 @@ void Overlay::spawn_badge_locked(int sprite, float x, float y) {
827849 m_spawn_times.push_back (now);
828850}
829851
852+ void Overlay::process_spawn_queue () {
853+ std::queue<SpawnRequest> local;
854+ {
855+ std::lock_guard<std::mutex> lock (m_spawn_queue_mutex);
856+ std::swap (local, m_spawn_queue);
857+ }
858+ while (!local.empty ()) {
859+ auto &request = local.front ();
860+ if (request.sprite .has_value ()) {
861+ spawn_badge (request.sprite .value (), request.x , request.y );
862+ } else {
863+ spawn_badge (request.x , request.y );
864+ }
865+ local.pop ();
866+ }
867+ }
868+
830869void Overlay::stop () { m_running = false ; }
831870
832871void Overlay::update (float dt) {
@@ -900,6 +939,7 @@ void Overlay::run(std::stop_token st) {
900939 if (m_has_pending_config.exchange (false , std::memory_order_acq_rel)) {
901940 apply_pending_config ();
902941 }
942+ process_spawn_queue ();
903943 auto frame = std::chrono::microseconds (m_frame_interval_us.load ());
904944 if (m_paused.load ()) {
905945 std::this_thread::sleep_for (frame);
0 commit comments