Skip to content

Commit df71ed0

Browse files
authored
Add ORBPathCallbacksExtended and isExtended flag support (#16)
1 parent 0f43179 commit df71ed0

File tree

13 files changed

+578
-96
lines changed

13 files changed

+578
-96
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ DerivedData/
99
.claude/
1010
.rb_template/
1111
.augment/
12+
/.build-debug

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
//
2+
// NestedCallbacks.cpp
3+
// OpenRenderBox
4+
5+
// TODO: Implement NestedCallbacks
6+
7+
#include <OpenRenderBoxCxx/Path/NestedCallbacks.hpp>
8+
#include <mutex>
9+
#if ORB_TARGET_OS_DARWIN
10+
#include <os/lock.h>
11+
#endif
12+
#include <OpenRenderBoxCxx/Vector/vector.hpp>
13+
14+
namespace ORB {
15+
namespace Path {
16+
17+
#if ORB_TARGET_OS_DARWIN
18+
19+
namespace {
20+
21+
struct CacheEntry {
22+
const ORBPathCallbacks* callbacks;
23+
size_t index;
24+
const NestedCallbacks* result;
25+
};
26+
27+
struct Cache {
28+
ORB::vector<CacheEntry, 0, size_t> entries;
29+
os_unfair_lock lock;
30+
31+
Cache() : entries(), lock(OS_UNFAIR_LOCK_INIT) {}
32+
};
33+
34+
Cache* gCache = nullptr;
35+
36+
// Helper to call bezierOrder on callbacks
37+
inline uint32_t callBezierOrder(const void* object, const ORBPathCallbacks* callbacks) {
38+
if (callbacks->bezierOrder) {
39+
return callbacks->bezierOrder(object);
40+
}
41+
return 3; // Default: cubic bezier
42+
}
43+
44+
} // anonymous namespace
45+
46+
NestedCallbacks::NestedCallbacks(const ORBPathCallbacks* callbacks, size_t index)
47+
: extended{}
48+
, originalCallbacks(callbacks)
49+
, nestedIndex(index)
50+
{
51+
// Set isExtended flag (byte at offset 0x2)
52+
extended.flags.isExtended = 1;
53+
54+
// Copy retain/release from original callbacks
55+
extended.retain = reinterpret_cast<decltype(extended.retain)>(callbacks->retain);
56+
extended.release = reinterpret_cast<decltype(extended.release)>(callbacks->release);
57+
58+
extended.apply = +[](const void *object, void *info, ORBPathApplyCallback callback, const ORBPathCallbacksExtended *ext) -> bool {
59+
auto self = reinterpret_cast<const NestedCallbacks*>(ext);
60+
auto original = self->originalCallbacks;
61+
if (original->apply) {
62+
return original->apply(object, info, callback);
63+
}
64+
return true;
65+
};
66+
67+
extended.isEqual = nullptr;
68+
69+
extended.isEmpty = +[](const void *object, const ORBPathCallbacksExtended *ext) -> bool {
70+
auto self = reinterpret_cast<const NestedCallbacks*>(ext);
71+
auto original = self->originalCallbacks;
72+
if (original->apply) {
73+
// Use single_element_callback to detect emptiness
74+
struct Info {
75+
size_t count;
76+
size_t depth;
77+
bool sawClose;
78+
} info = {0, 0, false};
79+
info.depth = self->nestedIndex;
80+
original->apply(object, &info, NestedCallbacks::single_element_callback);
81+
return info.count == 1;
82+
}
83+
return true;
84+
};
85+
86+
extended.bezierOrder = +[](const void *object, const ORBPathCallbacksExtended *ext) -> uint32_t {
87+
auto self = reinterpret_cast<const NestedCallbacks*>(ext);
88+
return callBezierOrder(object, self->originalCallbacks);
89+
};
90+
91+
extended.isSingleElement = nullptr;
92+
extended.boundingRect = nullptr;
93+
extended.cgPath = nullptr;
94+
95+
extended.next = +[](const void *object, const ORBPathCallbacksExtended *ext) -> const ORBPathCallbacksExtended* {
96+
auto self = reinterpret_cast<const NestedCallbacks*>(ext);
97+
auto nextCallbacks = NestedCallbacks::get(self->originalCallbacks, self->nestedIndex + 1);
98+
return &nextCallbacks->extended;
99+
};
100+
}
101+
102+
const NestedCallbacks* NestedCallbacks::get(const ORBPathCallbacks* callbacks, size_t index) {
103+
// Lazy initialization of global cache
104+
static std::once_flag initFlag;
105+
std::call_once(initFlag, []() {
106+
gCache = new Cache();
107+
});
108+
109+
os_unfair_lock_lock(&gCache->lock);
110+
111+
// Binary search for existing entry
112+
auto& entries = gCache->entries;
113+
size_t lo = 0;
114+
size_t hi = entries.size();
115+
116+
while (lo < hi) {
117+
size_t mid = lo + (hi - lo) / 2;
118+
const auto& entry = entries[mid];
119+
120+
if (entry.callbacks < callbacks) {
121+
lo = mid + 1;
122+
} else if (entry.callbacks > callbacks) {
123+
hi = mid;
124+
} else if (entry.index < index) {
125+
lo = mid + 1;
126+
} else if (entry.index > index) {
127+
hi = mid;
128+
} else {
129+
// Found exact match
130+
const NestedCallbacks* result = entry.result;
131+
os_unfair_lock_unlock(&gCache->lock);
132+
return result;
133+
}
134+
}
135+
136+
// Not found, create new NestedCallbacks
137+
auto* nested = new NestedCallbacks(callbacks, index);
138+
139+
// Insert at position 'lo' to maintain sorted order
140+
entries.reserve(entries.size() + 1);
141+
142+
// Shift elements to make room
143+
if (lo < entries.size()) {
144+
// Use memmove for efficiency
145+
size_t count = entries.size() - lo;
146+
memmove(&entries[lo + 1], &entries[lo], count * sizeof(CacheEntry));
147+
}
148+
149+
// Insert new entry
150+
entries[lo] = CacheEntry{callbacks, index, nested};
151+
152+
os_unfair_lock_unlock(&gCache->lock);
153+
return nested;
154+
}
155+
156+
// Mask for nesting elements (bits 17-20,21-24): used to detect depth changes
157+
// From assembly: 0x1de0000 = bits for elements 17, 18, 19, 20, 22, 23, 24
158+
static constexpr uint32_t kNestingElementMask = 0x1de0000;
159+
160+
// Helper to check if an element affects nesting depth
161+
static inline bool isNestingElement(ORBPathElement element) {
162+
if (element > 0x18) return false;
163+
return ((1u << element) & kNestingElementMask) != 0;
164+
}
165+
166+
// Helper to check if element is in range 0x0-0x3 (move/line/quad/curve)
167+
static inline bool isDrawingElement(ORBPathElement element) {
168+
return element < 4;
169+
}
170+
171+
// apply_elements_callback: tracks nesting depth during path enumeration
172+
// Info structure: { context, callback, depth, elementCount, sawClose }
173+
bool NestedCallbacks::apply_elements_callback(void* info, ORBPathElement element, const double* points, const void* userInfo) {
174+
struct ApplyInfo {
175+
void* context;
176+
ORBPathApplyCallback callback;
177+
size_t depth;
178+
};
179+
auto* applyInfo = static_cast<ApplyInfo*>(info);
180+
181+
if (element > 0x18) {
182+
// Forward to user callback if we're at depth 0
183+
if (applyInfo->depth == 0) {
184+
return applyInfo->callback(applyInfo->context, element, points, userInfo);
185+
}
186+
return false;
187+
}
188+
189+
uint32_t mask = 1u << element;
190+
if (mask & kNestingElementMask) {
191+
// Nesting element: increment depth
192+
applyInfo->depth++;
193+
return false;
194+
}
195+
196+
if (element == 0x10) { // Close subpath
197+
if (applyInfo->depth > 0) {
198+
applyInfo->depth--;
199+
}
200+
return false;
201+
}
202+
203+
// Forward non-nesting elements at depth 0
204+
if (applyInfo->depth == 0) {
205+
return applyInfo->callback(applyInfo->context, element, points, userInfo);
206+
}
207+
return false;
208+
}
209+
210+
// single_element_callback: checks if path is a single element
211+
// Info structure: { depth, elementCount, sawClose }
212+
bool NestedCallbacks::single_element_callback(void* info, ORBPathElement element, const double* points, const void* userInfo) {
213+
struct SingleInfo {
214+
size_t depth;
215+
size_t count;
216+
bool sawClose;
217+
};
218+
auto* singleInfo = static_cast<SingleInfo*>(info);
219+
220+
if (element > 0x18) {
221+
// Elements > 0x18: count as elements at depth 0
222+
if (singleInfo->depth == singleInfo->count) {
223+
singleInfo->count++;
224+
if (element == 0x13) { // Specific element marker
225+
singleInfo->sawClose = false;
226+
} else {
227+
singleInfo->sawClose = true;
228+
}
229+
}
230+
return false;
231+
}
232+
233+
uint32_t mask = 1u << element;
234+
235+
// Handle nesting elements
236+
if ((mask & 0xf000) != 0) {
237+
// Elements 12-15
238+
if (singleInfo->depth == singleInfo->count) {
239+
singleInfo->sawClose = true;
240+
singleInfo->count++;
241+
}
242+
return false;
243+
}
244+
245+
if (mask & kNestingElementMask) {
246+
// Other nesting elements: increment depth
247+
if (singleInfo->depth == singleInfo->count) {
248+
singleInfo->count++;
249+
}
250+
return false;
251+
}
252+
253+
if (element == 0x10) { // Close subpath
254+
if (singleInfo->depth > 0) {
255+
singleInfo->depth--;
256+
}
257+
if (singleInfo->depth == singleInfo->count && singleInfo->sawClose) {
258+
singleInfo->sawClose = false;
259+
singleInfo->count++;
260+
}
261+
return false;
262+
}
263+
264+
// Drawing elements (0-3)
265+
if (isDrawingElement(element)) {
266+
if (singleInfo->depth == singleInfo->count) {
267+
singleInfo->sawClose = true;
268+
singleInfo->count++;
269+
}
270+
}
271+
272+
// Return true if count >= 2 (stop enumeration)
273+
return singleInfo->count >= 2;
274+
}
275+
276+
// apply_elements_fast: fast path for Storage
277+
bool NestedCallbacks::apply_elements_fast(const Storage& storage, void* info) {
278+
// TODO: Implement fast path using Storage iterator
279+
(void)storage;
280+
(void)info;
281+
return true;
282+
}
283+
284+
// single_element_fast: fast path for single element detection on Storage
285+
bool NestedCallbacks::single_element_fast(const Storage& storage, void* info) {
286+
// TODO: Implement fast path using Storage iterator
287+
(void)storage;
288+
(void)info;
289+
return true;
290+
}
291+
292+
// first_element: find first element at given nesting depth
293+
bool NestedCallbacks::first_element(const Storage& storage, void* iterator, size_t depth) {
294+
// TODO: Implement using Storage iterator
295+
(void)storage;
296+
(void)iterator;
297+
(void)depth;
298+
return false;
299+
}
300+
301+
#endif /* ORB_TARGET_OS_DARWIN */
302+
303+
} /* namespace Path */
304+
} /* namespace ORB */
305+

0 commit comments

Comments
 (0)