GDevelop Core
Core library for developing platforms and tools compatible with GDevelop.
MemoryTrackedRegistry.h
1 /*
2  * GDevelop Core
3  * Copyright 2008-2016 Florian Rival ([email protected]). All rights
4  * reserved. This project is released under the MIT License.
5  */
6 #pragma once
7 
8 #include <algorithm>
9 #include <array>
10 #include <cstdint>
11 #include <cstring>
12 #include <string>
13 #include <unordered_map>
14 #include <unordered_set>
15 
16 #ifdef __EMSCRIPTEN__
17 #include <emscripten.h>
18 #else
19 #include <chrono>
20 #endif
21 
22 #include "GDCore/String.h"
23 
24 namespace gd {
25 
41  public:
42  // Internal C++ API (used by MemoryTracked).
43  static void add(const void* ptr, const char* className) {
44  if (!className) return;
45  dead()[className].erase(ptr);
46  alive()[className].insert(ptr);
47  }
48 
49  static void remove(const void* ptr, const char* className) {
50  if (!className) return;
51  alive()[className].erase(ptr);
52  dead()[className].insert(ptr);
53  deadContexts()[className].push(ptr, currentCallContextId_(), nowMs());
54  }
55 
56  static bool isDead(const void* ptr, const char* className) {
57  if (!className) return false;
58  auto it = dead().find(className);
59  return it != dead().end() && it->second.count(ptr) > 0;
60  }
61 
62  // WebIDL-facing API (called from JS via the binder).
63  static void add(long ptr, const gd::String& className) {
64  add(reinterpret_cast<const void*>(static_cast<uintptr_t>(ptr)),
65  className.c_str());
66  }
67 
68  static void remove(long ptr, const gd::String& className) {
69  remove(reinterpret_cast<const void*>(static_cast<uintptr_t>(ptr)),
70  className.c_str());
71  }
72 
73  static bool isDead(long ptr, const gd::String& className) {
74  return isDead(
75  reinterpret_cast<const void*>(static_cast<uintptr_t>(ptr)),
76  className.c_str());
77  }
78 
79  static long getDeadCount() {
80  long total = 0;
81  for (auto& kv : dead()) total += static_cast<long>(kv.second.size());
82  return total;
83  }
84 
85  static void pruneDead(long maxSize) {
86  if (getDeadCount() > maxSize) {
87  dead().clear();
88  deadContexts().clear();
89  }
90  }
91 
92  static long getAliveCount() {
93  long total = 0;
94  for (auto& kv : alive()) total += static_cast<long>(kv.second.size());
95  return total;
96  }
97 
98  static long getAliveCountForClass(const gd::String& className) {
99  auto it = alive().find(className.c_str());
100  return it != alive().end() ? static_cast<long>(it->second.size()) : 0;
101  }
102 
103  static long getDeadCountForClass(const gd::String& className) {
104  auto it = dead().find(className.c_str());
105  return it != dead().end() ? static_cast<long>(it->second.size()) : 0;
106  }
107 
108  // --- Destruction context API ---
109 
111  static void setCurrentCallContextId(long id) {
112  currentCallContextId_() = static_cast<int>(id);
113  }
114 
119  static long getDeadContextId(long ptr, const gd::String& className) {
120  auto* ctx = findDeadContext(
121  reinterpret_cast<const void*>(static_cast<uintptr_t>(ptr)),
122  className.c_str());
123  return ctx ? static_cast<long>(ctx->callContextId) : -1;
124  }
125 
130  static double getDeadContextTimeMs(long ptr, const gd::String& className) {
131  auto* ctx = findDeadContext(
132  reinterpret_cast<const void*>(static_cast<uintptr_t>(ptr)),
133  className.c_str());
134  return ctx ? ctx->timestampMs : 0.0;
135  }
136 
137  private:
138  using PtrSet = std::unordered_set<const void*>;
139  using ClassMap = std::unordered_map<std::string, PtrSet>;
140 
141  // --- Destruction context ring buffer ---
142 
143  struct DestructionContext {
144  const void* ptr = nullptr;
145  int callContextId = -1;
146  double timestampMs = 0.0;
147  };
148 
149  static constexpr size_t kRingCapacity = 64;
150 
151  struct ClassRing {
152  std::array<DestructionContext, kRingCapacity> entries{};
153  size_t count = 0; // total pushes; head = count % kRingCapacity
154 
155  void push(const void* p, int contextId, double timestamp) {
156  DestructionContext& entry = entries[count % kRingCapacity];
157  entry.ptr = p;
158  entry.callContextId = contextId;
159  entry.timestampMs = timestamp;
160  count++;
161  }
162 
163  // Search newest-first so the most recent destruction of a ptr wins.
164  const DestructionContext* find(const void* p) const {
165  size_t n = std::min(count, kRingCapacity);
166  for (size_t i = 0; i < n; i++) {
167  size_t idx = (count - 1 - i) % kRingCapacity;
168  if (entries[idx].ptr == p) return &entries[idx];
169  }
170  return nullptr;
171  }
172  };
173 
174  using RingMap = std::unordered_map<std::string, ClassRing>;
175 
176  // Heap-allocated maps (intentionally leaked) to avoid static destruction
177  // order issues.
178  static ClassMap& alive() {
179  static auto* m = new ClassMap();
180  return *m;
181  }
182 
183  static ClassMap& dead() {
184  static auto* m = new ClassMap();
185  return *m;
186  }
187 
188  static RingMap& deadContexts() {
189  static auto* m = new RingMap();
190  return *m;
191  }
192 
193  static int& currentCallContextId_() {
194  static int id = 0;
195  return id;
196  }
197 
201  static double nowMs() {
202 #ifdef __EMSCRIPTEN__
203  return emscripten_get_now();
204 #else
205  return static_cast<double>(
206  std::chrono::duration_cast<std::chrono::milliseconds>(
207  std::chrono::system_clock::now().time_since_epoch())
208  .count());
209 #endif
210  }
211 
212  static const DestructionContext* findDeadContext(const void* ptr,
213  const char* className) {
214  if (!className) return nullptr;
215  auto it = deadContexts().find(className);
216  if (it == deadContexts().end()) return nullptr;
217  return it->second.find(ptr);
218  }
219 };
220 
239  public:
240  MemoryTracked(const void* owner, const char* className)
241  : owner_(owner), className_(className) {
242  if (className_) MemoryTrackedRegistry::add(owner_, className_);
243  }
244 
245  ~MemoryTracked() { if (className_) MemoryTrackedRegistry::remove(owner_, className_); }
246 
247  // Non-copyable, non-movable.
248  MemoryTracked(const MemoryTracked&) = delete;
249  MemoryTracked& operator=(const MemoryTracked&) = delete;
250  MemoryTracked(MemoryTracked&&) = delete;
251  MemoryTracked& operator=(MemoryTracked&&) = delete;
252 
253  private:
254  const void* owner_;
255  const char* className_;
256 };
257 
258 } // namespace gd
A non-copyable, non-movable member object that registers/unregisters its owner with MemoryTrackedRegi...
Definition: MemoryTrackedRegistry.h:238
A static registry tracking the lifetime of C++ objects exposed to JavaScript via Emscripten/WebIDL bi...
Definition: MemoryTrackedRegistry.h:40
static long getDeadContextId(long ptr, const gd::String &className)
Definition: MemoryTrackedRegistry.h:119
static void setCurrentCallContextId(long id)
Definition: MemoryTrackedRegistry.h:111
static double getDeadContextTimeMs(long ptr, const gd::String &className)
Definition: MemoryTrackedRegistry.h:130
String represents an UTF8 encoded string.
Definition: String.h:33
const char * c_str() const
Get the C-string.
Definition: String.h:360
Definition: CommonTools.h:24