corecdtl 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,284 +0,0 @@
1
- #include "cpool.h"
2
- #include <iostream>
3
- #include <stdexcept>
4
-
5
- Napi::Function CPool::GetClass(Napi::Env env) {
6
- return DefineClass(env, "CPool", {
7
- InstanceMethod("initializePool", &CPool::InitializePool),
8
- InstanceMethod("registerObj", &CPool::RegisterObj),
9
- InstanceMethod("allocate", &CPool::Allocate),
10
- InstanceMethod("free", &CPool::Free),
11
- InstanceMethod("resizePool", &CPool::ResizePool),
12
- });
13
- }
14
-
15
- CPool::CPool(const Napi::CallbackInfo& info)
16
- : Napi::ObjectWrap<CPool>(info) {
17
- m_activeSize = 0;
18
- m_currentSize = 0;
19
- m_retiredCount = 0;
20
- m_shrinking = false;
21
- }
22
-
23
- CPool::~CPool() {
24
- for (auto &e : m_poolEntries) {
25
- if (!e.jsRef.IsEmpty()) e.jsRef.Unref();
26
- }
27
- m_poolEntries.clear();
28
- m_freeStack.clear();
29
- }
30
-
31
- __attribute__((always_inline))
32
- void CPool::pushFreeIndex(int idx) {
33
- m_freeStack.push_back(idx);
34
- }
35
-
36
- __attribute__((always_inline))
37
- int CPool::popFreeIndex() {
38
- if (m_freeStack.empty()) [[unlikely]] return -1;
39
- int idx = m_freeStack.back();
40
- m_freeStack.pop_back();
41
- return idx;
42
- }
43
-
44
- Napi::Value CPool::InitializePool(const Napi::CallbackInfo& info) {
45
- Napi::Env env = info.Env();
46
- if (info.Length() < 1 || !info[0].IsNumber()) {
47
- Napi::TypeError::New(env, "Pool size must be a number").ThrowAsJavaScriptException();
48
- return env.Null();
49
- }
50
- size_t newSize = info[0].As<Napi::Number>().Uint32Value();
51
- if (newSize == 0) {
52
- Napi::Error::New(env, "Pool size must be > 0").ThrowAsJavaScriptException();
53
- return env.Null();
54
- }
55
- if (m_currentSize != 0) {
56
- Napi::Error::New(env, "Pool already initialized").ThrowAsJavaScriptException();
57
- return env.Null();
58
- }
59
-
60
- try {
61
- m_poolEntries.resize(newSize);
62
- m_freeStack.reserve(newSize);
63
- } catch (const std::bad_alloc&) {
64
- Napi::Error::New(env, "Allocation failed").ThrowAsJavaScriptException();
65
- return env.Null();
66
- }
67
-
68
- m_currentSize = newSize;
69
- m_activeSize = newSize;
70
- m_retiredCount = 0;
71
- m_shrinking = false;
72
-
73
- // Initially all slots are free for registration and allocation.
74
- for (size_t i = 0; i < (size_t)newSize; ++i) {
75
- m_poolEntries[i].inUse = false;
76
- pushFreeIndex((int)i);
77
- }
78
-
79
- return env.Undefined();
80
- }
81
-
82
- Napi::Value CPool::RegisterObj(const Napi::CallbackInfo& info) {
83
- Napi::Env env = info.Env();
84
- if (info.Length() < 1 || !info[0].IsObject()) {
85
- Napi::TypeError::New(env, "RegisterObj expects an object").ThrowAsJavaScriptException();
86
- return env.Null();
87
- }
88
- if (m_currentSize == 0) {
89
- Napi::Error::New(env, "Pool not initialized").ThrowAsJavaScriptException();
90
- return env.Null();
91
- }
92
-
93
- // Find an index that currently has no jsRef assigned.
94
- int found = -1;
95
- for (size_t i = 0; i < m_currentSize; ++i) {
96
- if (m_poolEntries[i].jsRef.IsEmpty()) {
97
- found = (int)i;
98
- break;
99
- }
100
- }
101
- if (found == -1) {
102
- Napi::Error::New(env, "No free registration slot").ThrowAsJavaScriptException();
103
- return env.Null();
104
- }
105
-
106
- Napi::Object obj = info[0].As<Napi::Object>();
107
- m_poolEntries[found].jsRef = Napi::Persistent(obj);
108
- // keep it weak in sense we managed lifetime; do not call Ref here to avoid keeping V8 alive unnecessarily
109
- // but persistent already increases refcount; if you want to manage GC, call Ref/Unref appropriately.
110
-
111
- return Napi::Number::New(env, found);
112
- }
113
-
114
- Napi::Value CPool::Allocate(const Napi::CallbackInfo& info) {
115
- Napi::Env env = info.Env();
116
-
117
- if (m_activeSize == 0) [[unlikely]] {
118
- return env.Null();
119
- }
120
-
121
- // if shrinking and retired region exists, still can allocate from active region only
122
- int idx = popFreeIndex();
123
- if (idx == -1) [[unlikely]] {
124
- // no free slot
125
- return env.Null();
126
- }
127
-
128
- // Safety: If popped index is >= activeSize (retired area) -> put back and fail
129
- if ((size_t)idx >= m_activeSize) [[unlikely]] {
130
- // returned index belongs to retired area, push it back and fail allocation
131
- pushFreeIndex(idx);
132
- return env.Null();
133
- }
134
-
135
- PoolEntry& entry = m_poolEntries[idx];
136
- entry.inUse = true;
137
-
138
- // Return the JS object or null if not registered
139
- if (entry.jsRef.IsEmpty()) [[likely]] {
140
- // Not registered JS object — still return null (caller must handle)
141
- return env.Null();
142
- }
143
-
144
- return entry.jsRef.Value();
145
- }
146
-
147
- Napi::Value CPool::Free(const Napi::CallbackInfo& info) {
148
- Napi::Env env = info.Env();
149
- if (info.Length() < 1 || !info[0].IsNumber()) [[unlikely]] {
150
- Napi::TypeError::New(env, "Free expects index number").ThrowAsJavaScriptException();
151
- return env.Null();
152
- }
153
- int idx = info[0].As<Napi::Number>().Int32Value();
154
- if (idx < 0 || (size_t)idx >= m_currentSize) [[unlikely]] {
155
- Napi::RangeError::New(env, "Index out of range").ThrowAsJavaScriptException();
156
- return env.Null();
157
- }
158
-
159
- PoolEntry& entry = m_poolEntries[idx];
160
- if (!entry.inUse) [[unlikely]] {
161
- // double free - ignore silently (or optionally log)
162
- return env.Undefined();
163
- }
164
-
165
- entry.inUse = false;
166
-
167
- // If this index is in retired area, we must Unref the jsRef and decrease retired count.
168
- if ((size_t)idx >= m_activeSize) [[unlikely]] {
169
- // unref persistent ref if set
170
- if (!entry.jsRef.IsEmpty()) {
171
- entry.jsRef.Unref();
172
- entry.jsRef = Napi::ObjectReference();
173
- }
174
- if (m_retiredCount > 0) {
175
- --m_retiredCount;
176
- if (m_retiredCount == 0 && m_shrinking) {
177
- finalizeShrinkIfNeeded(env);
178
- return env.Undefined();
179
- }
180
- }
181
- // we don't push retired indices back into freeStack because retired area will be shrunk away
182
- return env.Undefined();
183
- }
184
-
185
- // normal free -> push back to free list
186
- pushFreeIndex(idx);
187
- return env.Undefined();
188
- }
189
-
190
- void CPool::finalizeShrinkIfNeeded(Napi::Env env) {
191
- // physically release retired area
192
- if (!m_shrinking) return;
193
- for (size_t i = m_activeSize; i < m_currentSize; ++i) {
194
- if (!m_poolEntries[i].jsRef.IsEmpty()) {
195
- m_poolEntries[i].jsRef.Unref();
196
- m_poolEntries[i].jsRef = Napi::ObjectReference();
197
- }
198
- }
199
- m_poolEntries.resize(m_activeSize);
200
- m_currentSize = m_activeSize;
201
- // rebuild freeStack to contain only indices < activeSize that are free
202
- m_freeStack.clear();
203
- m_freeStack.reserve(m_activeSize);
204
- for (size_t i = 0; i < m_activeSize; ++i) {
205
- if (!m_poolEntries[i].inUse) pushFreeIndex((int)i);
206
- }
207
- m_shrinking = false;
208
- }
209
-
210
- Napi::Value CPool::ResizePool(const Napi::CallbackInfo& info) {
211
- Napi::Env env = info.Env();
212
- if (info.Length() < 1 || !info[0].IsNumber()) {
213
- Napi::TypeError::New(env, "ResizePool expects a number").ThrowAsJavaScriptException();
214
- return env.Null();
215
- }
216
- size_t newSize = info[0].As<Napi::Number>().Uint32Value();
217
- if (newSize == 0) {
218
- Napi::Error::New(env, "new size must be > 0").ThrowAsJavaScriptException();
219
- return env.Null();
220
- }
221
- if (newSize == m_activeSize) return env.Undefined();
222
-
223
- if (newSize > m_currentSize) {
224
- // expand physical vector
225
- try {
226
- size_t old = m_currentSize;
227
- m_poolEntries.resize(newSize);
228
- // initialize new slots as free and push them to freeStack if they are within active area
229
- for (size_t i = old; i < newSize; ++i) {
230
- m_poolEntries[i].inUse = false;
231
- }
232
- // add new indices to freeStack for the extended active area
233
- for (size_t i = old; i < newSize; ++i) {
234
- pushFreeIndex((int)i);
235
- }
236
- m_currentSize = newSize;
237
- m_activeSize = newSize;
238
- } catch (const std::bad_alloc&) {
239
- Napi::Error::New(env, "allocation failed").ThrowAsJavaScriptException();
240
- return env.Null();
241
- }
242
- } else {
243
- // shrinking -> mark activeSize and if there are in-use slots in retired area mark retiring
244
- size_t retiredStart = newSize;
245
- size_t activeInRetired = 0;
246
- for (size_t i = retiredStart; i < m_currentSize; ++i) {
247
- if (m_poolEntries[i].inUse) activeInRetired++;
248
- }
249
-
250
- m_activeSize = newSize;
251
-
252
- if (activeInRetired == 0) {
253
- // safe to immediately shrink: unref jsRefs and resize
254
- for (size_t i = retiredStart; i < m_currentSize; ++i) {
255
- if (!m_poolEntries[i].jsRef.IsEmpty()) {
256
- m_poolEntries[i].jsRef.Unref();
257
- m_poolEntries[i].jsRef = Napi::ObjectReference();
258
- }
259
- }
260
- m_poolEntries.resize(m_activeSize);
261
- m_currentSize = m_activeSize;
262
- // rebuild freeStack
263
- m_freeStack.clear();
264
- m_freeStack.reserve(m_activeSize);
265
- for (size_t i = 0; i < m_activeSize; ++i) {
266
- if (!m_poolEntries[i].inUse) pushFreeIndex((int)i);
267
- }
268
- } else {
269
- // there are active entries in retired area -> mark for shrink
270
- m_retiredCount = activeInRetired;
271
- m_shrinking = true;
272
- // remove retired indices from freeStack if any (they shouldn't be free)
273
- std::vector<int> newStack;
274
- newStack.reserve(m_freeStack.size());
275
- for (int idx : m_freeStack) {
276
- if ((size_t)idx < m_activeSize) newStack.push_back(idx);
277
- }
278
- m_freeStack.swap(newStack);
279
- // retired indices remain until freed; when freed, Free() will decrement m_retiredCount and finalize
280
- }
281
- }
282
-
283
- return env.Undefined();
284
- }
@@ -1,39 +0,0 @@
1
- #pragma once
2
- #include <napi.h>
3
- #include <vector>
4
- #include <stack>
5
- #include <functional>
6
-
7
- struct PoolEntry {
8
- Napi::ObjectReference jsRef; // persistent JS object
9
- bool inUse = false; // true when allocated
10
- // optionally other meta fields...
11
- };
12
-
13
- class CPool : public Napi::ObjectWrap<CPool> {
14
- public:
15
- static Napi::Function GetClass(Napi::Env env);
16
- CPool(const Napi::CallbackInfo& info);
17
- ~CPool();
18
-
19
- // NAPI methods
20
- Napi::Value InitializePool(const Napi::CallbackInfo& info);
21
- Napi::Value RegisterObj(const Napi::CallbackInfo& info);
22
- Napi::Value Allocate(const Napi::CallbackInfo& info);
23
- Napi::Value Free(const Napi::CallbackInfo& info);
24
- Napi::Value ResizePool(const Napi::CallbackInfo& info);
25
-
26
- private:
27
- // core data
28
- std::vector<PoolEntry> m_poolEntries;
29
- std::vector<int> m_freeStack; // indices of free entries (LIFO)
30
- size_t m_activeSize = 0; // visible active capacity
31
- size_t m_currentSize = 0; // physical vector size
32
- size_t m_retiredCount = 0; // number of in-use entries in retired zone
33
- bool m_shrinking = false; // indicates shrink process in progress
34
-
35
- // helpers
36
- void pushFreeIndex(int idx);
37
- int popFreeIndex(); // -1 if none
38
- void finalizeShrinkIfNeeded(Napi::Env env);
39
- };
@@ -1,31 +0,0 @@
1
- #pragma once
2
- #include <unordered_map>
3
- #include "asset_meta.h"
4
-
5
- namespace Asset {
6
- size_t UrlHash::operator()(const UrlKey& k) const noexcept {
7
- // FNV-1a 64-bit
8
- size_t h = 1469598103934665603ULL;
9
- for (size_t i = 0; i < k.len; ++i) {
10
- h ^= static_cast<uint8_t>(k.data[i]);
11
- h *= 1099511628211ULL;
12
- }
13
- return h;
14
- }
15
-
16
- bool UrlEq::operator()(const UrlKey& a, const UrlKey& b) const noexcept {
17
- return a == b;
18
- }
19
-
20
- AssetIndex::AssetIndex() = default;
21
-
22
- void AssetIndex::add(const char* path, uint32_t len, AssetMeta* meta) {
23
- index.emplace(UrlKey{ path, (size_t)len }, meta);
24
- }
25
-
26
- AssetMeta* AssetIndex::find(const char* path, uint32_t len) const noexcept {
27
- UrlKey key{ path, len };
28
- auto it = index.find(key);
29
- return it == index.end() ? nullptr : it->second;
30
- }
31
- }
@@ -1,53 +0,0 @@
1
- #pragma once
2
- #include <cstdint>
3
- #include <cstring>
4
- #include <unordered_map>
5
-
6
- namespace Asset {
7
- enum class CacheKind : uint8_t {
8
- RAM,
9
- MMAP,
10
- SENDFILE
11
- };
12
-
13
- struct UrlKey {
14
- const char* data;
15
- size_t len;
16
-
17
- bool operator==(const UrlKey& other) const {
18
- if (len != other.len) return false;
19
- return std::memcmp(data, other.data, len) == 0;
20
- }
21
- };
22
-
23
- struct UrlHash {
24
- size_t operator()(const UrlKey& k) const noexcept;
25
- };
26
-
27
- struct UrlEq {
28
- bool operator()(const UrlKey& a, const UrlKey& b) const noexcept;
29
- };
30
-
31
-
32
- struct AssetMeta {
33
- const char* path;
34
- uint32_t pathLen;
35
- int fd;
36
- uint64_t size;
37
- uint64_t mtime;
38
- CacheKind kind;
39
- void* data;
40
- uint64_t dataLen;
41
- uint64_t etag;
42
- };
43
-
44
- class AssetIndex {
45
- public:
46
- using MapType = std::unordered_map<UrlKey, AssetMeta*, UrlHash, UrlEq>;
47
- AssetIndex();
48
- void add(const char* path, uint32_t len, AssetMeta* meta);
49
- AssetMeta* find(const char* path, uint32_t len) const noexcept;
50
- private:
51
- MapType index;
52
- };
53
- }
@@ -1,52 +0,0 @@
1
- #include "asset_parser.h"
2
- #include "asset_meta.h"
3
- #include <iostream>
4
-
5
- using namespace Asset;
6
-
7
- __attribute__((always_inline))
8
- CacheKind decideCacheKind(uint64_t size) {
9
- if (size <= 64 * 1024)
10
- return Asset::CacheKind::RAM;
11
- if (size <= 2 * 1024 * 1024)
12
- return Asset::CacheKind::MMAP;
13
- return Asset::CacheKind::SENDFILE;
14
- }
15
-
16
- Napi::Function PublicAssetParser::GetClass(Napi::Env env) {
17
- return DefineClass(env, "PublicAssetParser", {
18
- InstanceMethod("setAssetRoute", &PublicAssetParser::SetAssetRoute),
19
- InstanceMethod("handlePublicAsset", &PublicAssetParser::HandlePublicAsset)
20
- });
21
- }
22
-
23
- PublicAssetParser::PublicAssetParser(const Napi::CallbackInfo& info)
24
- : Napi::ObjectWrap<PublicAssetParser>(info),
25
- assetRouteLen(0) {}
26
-
27
- void PublicAssetParser::SetAssetRoute(const Napi::CallbackInfo& info) {
28
- const std::string route = info[0].As<Napi::String>();
29
- assetRouteName = route;
30
- assetRouteLen = route.length();
31
- }
32
-
33
- Napi::Value PublicAssetParser::HandlePublicAsset(
34
- const Napi::CallbackInfo& info
35
- ) {
36
- Napi::Env env = info.Env();
37
-
38
- const char* buf = info[0].As<Napi::Buffer<char>>().Data();
39
- size_t startOffset = info[1].As<Napi::Number>().Uint32Value();
40
-
41
- size_t i = startOffset + assetRouteLen;
42
- size_t begin = i;
43
-
44
- while (buf[i] != '?' && buf[i] != ' ' && buf[i] != '\0') {
45
- ++i;
46
- }
47
-
48
- size_t pathLen = i - begin;
49
- const char* path = buf + begin;
50
-
51
- return Napi::String::New(env, path, pathLen);
52
- }
@@ -1,20 +0,0 @@
1
- #pragma once
2
- #include <napi.h>
3
- #include <string>
4
- #include "asset_meta.h"
5
-
6
- class PublicAssetParser : public Napi::ObjectWrap<PublicAssetParser> {
7
- public:
8
- static Napi::Function GetClass(Napi::Env env);
9
-
10
- PublicAssetParser(const Napi::CallbackInfo& info);
11
- ~PublicAssetParser() = default;
12
-
13
- void SetAssetRoute(const Napi::CallbackInfo& info);
14
- Napi::Value HandlePublicAsset(const Napi::CallbackInfo& info);
15
-
16
- private:
17
- std::string assetRouteName;
18
- size_t assetRouteLen;
19
- Asset::AssetIndex assetIndex;
20
- };
@@ -1,85 +0,0 @@
1
- #pragma once
2
- #include <cstdint>
3
- #include <vector>
4
- #include <napi.h>
5
-
6
- using namespace std;
7
-
8
- namespace RouteBuilder {
9
- enum ParamType { kString = 1, kNumber = 2 };
10
- /// Endpoint parameter metadata.
11
- struct EndpointParam {
12
- std::string name;
13
- ParamType type = ParamType::kString;
14
- };
15
-
16
-
17
- /// Endpoint description used by the builder.
18
- struct Endpoint {
19
- const char* url; ///< Null-terminated URL pattern (e.g. "users/:id/")
20
- std::vector<EndpointParam> params;
21
- int vptr_table_index = -1;
22
-
23
- explicit operator bool() const {
24
- return vptr_table_index != -1;
25
- }
26
- };
27
-
28
- /// Route trie node layout. We purposefully separate 'hot' data from
29
- /// 'cold' data: small fixed-size fields come first for cache locality.
30
- struct alignas(64) RouteNode {
31
- // HOT data
32
- uint64_t value = 0; ///< Packed up to 8 characters for fast comparison
33
- size_t value_length = 0; ///< Number of meaningful bytes in `value`
34
- int vptr_table_index = -1; ///< Handler index if this node terminates an endpoint
35
- ParamType param_type = ParamType::kString;
36
- char _padding[7] = {};
37
- bool is_param = false;
38
- bool is_wildcard = false;
39
-
40
-
41
- // COLD data
42
- std::string param_name; ///< Name of the parameter (if is_param)
43
- std::vector<std::shared_ptr<RouteNode>> children;///< Child nodes
44
- std::weak_ptr<RouteNode> parent; ///< Optional parent pointer
45
- };
46
-
47
-
48
- // static_assert(sizeof(RouteNode) % 64 == 0, "RouteNode must be 64-byte aligned multiple");
49
-
50
-
51
- // Public API
52
-
53
-
54
- /**
55
- * @brief Build a route tree from a Endpoints Eps.
56
- *
57
- * The caller retains ownership of Endpoint; the function copies endpoint
58
- * descriptors as needed. Returns nullptr if table empty.
59
- */
60
- std::shared_ptr<RouteNode> buildRouteTree(const std::vector<Endpoint>& eps) noexcept;
61
-
62
-
63
- /**
64
- * @brief Match a URL against a previously-built route tree.
65
- *
66
- * The returned unique_ptr holds the match result. Use vptr_table_index to
67
- * dispatch to the appropriate handler.
68
- */
69
- int matchUrl(
70
- Napi::Env env,
71
- const std::shared_ptr<RouteNode>& root,
72
- const char* url,
73
- size_t urlLen,
74
- uint32_t* offset,
75
- Napi::Array* pathParams,
76
- Napi::Object* queryParams,
77
- uint32_t query_limit
78
- ) noexcept;
79
-
80
-
81
- /**
82
- * @brief Debug helper: print the route tree (human-readable).
83
- */
84
- void printRouteTree(const std::shared_ptr<RouteNode>& node, int depth = 0) noexcept;
85
- } // namespace RouteBuilder