Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
ChunkedArrayTextStore.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include "ChunkedArrayTextStore.h"
7
8using namespace Stroika::Foundation;
9using namespace Stroika::Foundation::Memory;
10
11using namespace Stroika::Frameworks;
12using namespace Stroika::Frameworks::Led;
13
14class ChunkedArrayTextStore::TextChunk {
15public:
16 TextChunk () = default;
17 TextChunk (const Led_tChar* copyFrom, size_t bytesToCopy);
18
19public:
20 enum {
21 kTextChunkSize = (4096 - sizeof (size_t) - 8) / sizeof (Led_tChar)
22 }; // good guess as to how big we can allocate and still have stdlib allocator
23 // not waste any space for rounding up
24
25 /*
26 * All indexes are relative to this TextChunk - and it is a programming error to
27 * insert past end of buffer - or to delete too many characters.
28 */
29public:
30 nonvirtual size_t GetLength () const noexcept;
31 nonvirtual size_t GetBytesCanAccommodate () const noexcept;
32 nonvirtual void CopyOut (size_t from, size_t count, Led_tChar* buffer) const noexcept;
33 nonvirtual const Led_tChar* PeekAfter (size_t charPos) const noexcept;
34 nonvirtual void InsertAfter (const Led_tChar* what, size_t howMany, size_t after) noexcept;
35 nonvirtual void DeleteAfter (size_t howMany, size_t after) noexcept;
36
37private:
38 size_t fTotalTcharsUsed{0};
39 Led_tChar fData[kTextChunkSize];
40};
41
42// class ChunkedArrayTextStore::TextChunk
43inline ChunkedArrayTextStore::TextChunk::TextChunk (const Led_tChar* copyFrom, size_t bytesToCopy)
44 : fTotalTcharsUsed{bytesToCopy}
45{
46 Assert (bytesToCopy <= kTextChunkSize);
47 AssertNotNull (copyFrom);
48 (void)::memcpy (fData, copyFrom, bytesToCopy * sizeof (Led_tChar));
49}
50inline size_t ChunkedArrayTextStore::TextChunk::GetLength () const noexcept
51{
52 return (fTotalTcharsUsed);
53}
54inline size_t ChunkedArrayTextStore::TextChunk::GetBytesCanAccommodate () const noexcept
55{
56 return (kTextChunkSize - fTotalTcharsUsed);
57}
58inline void ChunkedArrayTextStore::TextChunk::CopyOut (size_t from, size_t count, Led_tChar* buffer) const noexcept
59{
60 AssertNotNull (buffer);
61 Assert (from + count <= fTotalTcharsUsed);
62 (void)::memcpy (buffer, &fData[from], count * sizeof (Led_tChar));
63}
64inline const Led_tChar* ChunkedArrayTextStore::TextChunk::PeekAfter (size_t charPos) const noexcept
65{
66 Assert (charPos >= 0);
67 Assert (charPos < fTotalTcharsUsed);
68 return (&fData[charPos]);
69}
70inline void ChunkedArrayTextStore::TextChunk::InsertAfter (const Led_tChar* what, size_t howMany, size_t after) noexcept
71{
72 Assert (what != 0 or howMany == 0);
73 Assert (after >= 0);
74 Assert (after <= fTotalTcharsUsed); // cannot insert past end (other than appending)
75 Assert (after + howMany <= kTextChunkSize);
76 /*
77 * If we are overwriting existing bytes- shovel them off to the right first.
78 * Then copyin the new data.
79 */
80 Assert (fTotalTcharsUsed >= after);
81 size_t bytesToMove = fTotalTcharsUsed - after;
82 if (bytesToMove != 0) {
83 (void)memmove (&fData[after + howMany], &fData[after], bytesToMove * sizeof (Led_tChar));
84 }
85 (void)::memcpy (&fData[after], what, howMany * sizeof (Led_tChar));
86 fTotalTcharsUsed += howMany;
87}
88inline void ChunkedArrayTextStore::TextChunk::DeleteAfter (size_t howMany, size_t after) noexcept
89{
90 Require (after + howMany <= fTotalTcharsUsed);
91 size_t bytesToMove = fTotalTcharsUsed - (after + howMany);
92 if (bytesToMove != 0) {
93 (void)::memmove (&fData[after], &fData[after + howMany], bytesToMove * sizeof (Led_tChar));
94 }
95 fTotalTcharsUsed -= howMany;
96}
97
98/*
99 * Tried a bunch of different sizes. Smaller sizes have produced no perceptable speed difference,
100 * and the DO require more memory (more hack markers), so I pick this size. And larger and I CAN
101 * see some slow down. -- LGP 950528
102 *
103 * Played around with this again briefly, and 50 still appears to be about the best
104 * value - LGP 960515.
105 */
106const size_t kEnufChildrenToApplyHackMarkers = 50;
107
108// This is what goes in a fTextStoreHook of a Marker* when we add it...
109class ChunkedArrayMarkerHook : public Marker::HookData, public Memory::UseBlockAllocationIfAppropriate<ChunkedArrayMarkerHook> {
110public:
111 ChunkedArrayMarkerHook () = default;
112
113public:
114 virtual MarkerOwner* GetOwner () const override;
115 virtual size_t GetStart () const override;
116 virtual size_t GetEnd () const override;
117 virtual size_t GetLength () const override;
118 virtual void GetStartEnd (size_t* start, size_t* end) const override;
119
120 nonvirtual void AddToChildList (Marker* marker);
121 nonvirtual size_t CountChildren () const;
122 nonvirtual bool CountChildrenMoreThan (size_t n) const; // return true iff at least n children
123
124 // NB: As far as I know - ordering in this linked list doesn't matter.
125 // Consider replacing it with a lhs/rhs child pointers and stricly having binary tree?
126 // Maybe not cuz then we lose our contains property for subtrees
127 Marker* fFirstSubMarker; // singly linked list of submarkers
128 Marker* fNextSubMarker; // (next link with same parent)
129
130 Marker* fParent{nullptr};
131 bool fIsHackMarker : 1 {false};
132 bool fIsDeletedMarker : 1 {false}; // true only for REAL markers who are deleted
133 bool fIsPreRemoved : 1 {false};
134 size_t fStart{0};
135 size_t fLength{0};
136 MarkerOwner* fOwner{nullptr};
137};
138
139// Subclass only to do block allocation
140class HackMarker : public Marker, public Memory::UseBlockAllocationIfAppropriate<HackMarker> {
141public:
142 HackMarker () = default;
143};
144
145MarkerOwner* ChunkedArrayMarkerHook::GetOwner () const
146{
147 //OLD COMMENT OBSOLETE - LGP 2000-07-25 - // NOT CLEAR IF WE SHOULD ALLOW NULL RETURN????
148 EnsureNotNull (fOwner); // LGP 2000-07-25 - this can never be NULL - cuz we always require a non-NULL marker owner when adding!
149 return fOwner;
150}
151
152size_t ChunkedArrayMarkerHook::GetStart () const
153{
154 Assert (fStart < 0x8000000); // a really big number - we don't have enough memory to trigger
155 // this - only point is to test of accidental cast of negnum to
156 // size_t.
157 return fStart;
158}
159
160size_t ChunkedArrayMarkerHook::GetEnd () const
161{
162 Assert (fStart < 0x8000000); // See GetStart
163 Assert (fLength < 0x8000000); // ''
164 return fStart + fLength;
165}
166
167size_t ChunkedArrayMarkerHook::GetLength () const
168{
169 Assert (fLength < 0x8000000); // See GetStart
170 return fLength;
171}
172
173void ChunkedArrayMarkerHook::GetStartEnd (size_t* start, size_t* end) const
174{
175 Assert (fStart < 0x8000000); // See GetStart
176 Assert (fLength < 0x8000000); // ''
177 RequireNotNull (start);
178 RequireNotNull (end);
179 *start = fStart;
180 *end = fStart + fLength;
181}
182
183/*
184 ********************************************************************************
185 ********************** Some private inline function definitions ****************
186 ********************************************************************************
187 */
188static inline ChunkedArrayMarkerHook* OurStuff (const Marker* marker)
189{
190 RequireNotNull (marker);
191 RequireNotNull ((ChunkedArrayMarkerHook*)marker->fTextStoreHook);
192 AssertMember (marker->fTextStoreHook, ChunkedArrayMarkerHook);
193 EnsureNotNull (marker->fTextStoreHook);
194 return (ChunkedArrayMarkerHook*)marker->fTextStoreHook;
195}
196
197inline void ChunkedArrayMarkerHook::AddToChildList (Marker* marker)
198{
199 AssertNotNull (marker);
200 OurStuff (marker)->fNextSubMarker = fFirstSubMarker;
201 fFirstSubMarker = marker;
202}
203inline size_t ChunkedArrayMarkerHook::CountChildren () const
204{
205 size_t nChildren = 0;
206 for (auto curChild = fFirstSubMarker; curChild != NULL; curChild = OurStuff (curChild)->fNextSubMarker) {
207 ++nChildren;
208 }
209 return nChildren;
210}
211inline bool ChunkedArrayMarkerHook::CountChildrenMoreThan (size_t n) const // return true iff at least n children
212{
213 size_t nChildren = 0;
214 for (auto curChild = fFirstSubMarker; curChild != NULL; curChild = OurStuff (curChild)->fNextSubMarker) {
215 ++nChildren;
216 if (nChildren >= n) {
217 return true;
218 }
219 }
220 return nChildren >= n;
221}
222inline bool ChunkedArrayTextStore::AllHackMarkers (const Marker* m)
223{
224 AssertNotNull (m);
225 return (OurStuff (m)->fIsHackMarker and (OurStuff (m)->fFirstSubMarker == NULL or AllSubMarkersAreHackMarkerTrees (m)));
226}
227inline ChunkedArrayTextStore::TextChunk* ChunkedArrayTextStore::AtomicAddChunk (size_t atArrayPos)
228{
229 TextChunk* t = new TextChunk ();
230 AssertNotNull (t);
231 try {
232 // fTextChunks.InsertAt (t, atArrayPos);
233 fTextChunks.insert (fTextChunks.begin () + atArrayPos, t);
234 }
235 catch (...) {
236 delete t;
237 throw;
238 }
239 AssertNotNull (t);
240 return (t);
241}
242
243static inline size_t GetMarkerStart_ (Marker* m)
244{
245 RequireNotNull (m);
246 Ensure (OurStuff (m)->fStart == m->GetStart ());
247 return OurStuff (m)->fStart;
248}
249static inline void SetMarkerStart_ (Marker* m, size_t s)
250{
251 OurStuff (m)->fStart = s;
252}
253static inline size_t GetMarkerLength_ (Marker* m)
254{
255 RequireNotNull (m);
256 Ensure (OurStuff (m)->fLength == m->GetLength ());
257 return OurStuff (m)->fLength;
258}
259static inline void SetMarkerLength_ (Marker* m, size_t s)
260{
261 OurStuff (m)->fLength = s;
262}
263static inline void SetMarkerOwner_ (Marker* m, MarkerOwner* o)
264{
265 OurStuff (m)->fOwner = o;
266}
267
268static inline bool QUICK_Overlap (ChunkedArrayMarkerHook* h, size_t from, size_t to)
269{
270 RequireNotNull (h);
271 /*
272 * Avoid virtual function call on m to get its range, for speed.
273 */
274 size_t start = h->fStart;
275 if (start > to) {
276 return false;
277 }
278 size_t end = start + h->fLength;
279
280 if ((from <= end) and (start <= to)) {
281 // Maybe overlap - handle nuanced cases of zero-sized overlaps
282 size_t overlapSize;
283 if (to >= end) {
284 size_t a = end - from;
285 size_t b = end - start;
286 overlapSize = min (a, b);
287 }
288 else {
289 size_t a = to - from;
290 size_t b = to - start;
291 overlapSize = min (a, b);
292 }
293 if (overlapSize == 0) {
294 return end == start;
295 }
296 else {
297 return true;
298 }
299 }
300 else {
301 return false;
302 }
303}
304static inline bool QUICK_Overlap (const Marker& m, size_t from, size_t to)
305{
306 bool result = QUICK_Overlap (OurStuff (&m), from, to);
307 Assert (result == TextStore::Overlap (m, from, to));
308 return result;
309}
310static inline bool QUICK_Contains (size_t containedStart, size_t containedEnd, size_t containerStart, size_t containerEnd)
311{
312 return (containedStart >= containerStart) and (containerEnd >= containedEnd);
313}
314static inline bool QUICK_Contains (const Marker& containedMarker, const Marker& containerMarker)
315{
316 ChunkedArrayMarkerHook* h = OurStuff (&containerMarker);
317 size_t containerStart = h->fStart;
318 size_t containerEnd = containerStart + h->fLength;
319
320 ChunkedArrayMarkerHook* h1 = OurStuff (&containedMarker);
321 size_t containedStart = h1->fStart;
322 size_t containedEnd = containedStart + h1->fLength;
323 ;
324 bool result = QUICK_Contains (containedStart, containedEnd, containerStart, containerEnd);
325 Assert (result == Contains (containedMarker, containerMarker));
326 return result;
327}
328
329#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
330struct ChunkedArrayTextStore::CollectLookupCacheElt {
331 vector<Marker*> fMarkers;
332 size_t fFrom;
333 size_t fTo;
334
335 struct COMPARE_ITEM {
336 COMPARE_ITEM (size_t from, size_t to)
337 : fFrom (from)
338 , fTo (to)
339 {
340 }
341
342 size_t fFrom;
343 size_t fTo;
344 };
345
346 nonvirtual void Clear ()
347 {
348 fFrom = static_cast<size_t> (-1);
349 }
350 static bool Equal (const CollectLookupCacheElt& lhs, const COMPARE_ITEM& rhs)
351 {
352 return lhs.fFrom == rhs.fFrom and lhs.fTo == rhs.fTo;
353 }
354};
355#endif
356
357class ChunkedArrayMarkerOwnerHook : public MarkerOwner::HookData {
358private:
359 using inherited = MarkerOwner::HookData;
360
361public:
362 ChunkedArrayMarkerOwnerHook (MarkerOwner* mo, size_t len)
363 :
364#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
365 fCache{2}
366 ,
367#endif
368 fRootMarker{}
369#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
370 , fTotalMarkersPresent{0}
371 , fTotalHackMarkersPresent{0}
372 , fPeakTotalMarkersPresent{0}
373 , fPeakHackMarkersPresent{0}
374 , fTotalHackMarkersAlloced{0}
375#endif
376 {
377 Assert (fRootMarker.fTextStoreHook == NULL);
378 fRootMarker.fTextStoreHook = new ChunkedArrayMarkerHook ();
379 SetMarkerOwner_ (&fRootMarker, mo);
380 SetMarkerStart_ (&fRootMarker, 0);
381 SetMarkerLength_ (&fRootMarker, len);
382 }
383 ~ChunkedArrayMarkerOwnerHook ()
384 {
385#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
386 Require (fTotalMarkersPresent == 0); // ALL MARKERS MUST BE REMOVED BEFORE EDITOR DESTROYED!!!!
387 // If this assertion ever triggers, then it may well be a Led client
388 // bug and not a Led-bug per-se. Check that all your markers have been
389 // removed!
390 //
391 // See Led's FAQ#27 - http://www.sophists.com/Led/LedClassLib/ClassLibDocs/Recipes.html#27
392#endif
393
394//----Disabled this BUG WORKAROUND cleanup code again - now that its localized per-MarkerOwner -
395// maybe easier to debug if it ever comes up again - LGP 2000-07-26. NOTE - this code should NOT be enabled! except to
396// work around a Led ChunkedArrayTextStore bug which doesn't appear to exist anymore... LGP 2001-08-28 (3.0c1x).
397//
398//----Enabled this cleanup code again to avoid needless asserts in LEC's 5.0 product. Debug this later... Again...LGP980629
399//---- THIS CLEANUP CODE STILL BROKEN - SEEN ERROR SPORADICLY, BUT STILL NO REPRODUCIBLE CASE - Harmless anyhow - leave this as is - LGP 980406
400//---- tmp re-enable - and see if this is fixed, or perhaps debug it... - LGP 980316
401// Should not be needed anymore!!!
402// And yet it is!!! - try file natfta.txt!!! - LGP 950527 - See SPR 0300
403#if 0
404 // Walk children of root marker and see if any hack markers left... Any children left at this point MUST be
405 // hack markers.
406 {
407 // Note that FreeHackTree will remove the argument marker from the parents list, so we can just keep using
408 // the first child of fRootMarker til there are none...
409 for (Marker* mi = OurStuff (&fRootMarker)->fFirstSubMarker; mi != NULL; mi = OurStuff (&fRootMarker)->fFirstSubMarker) {
410 Assert (AllHackMarkers (mi));
411 FreeHackTree (mi);
412 }
413 }
414#endif
415
416#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
417 Assert (fTotalHackMarkersPresent == 0); // If the fTotalMarkersPresent==0 and fTotalHackMarkersPresent != 0, then
418 // this is a Led bug (see walk children/FreeHackTree code above for workaround)
419#endif
420
421 Assert (OurStuff (&fRootMarker)->fFirstSubMarker == NULL); // Everything should be deleted by now
422
423 if (fRootMarker.fTextStoreHook != NULL) {
424 SetMarkerOwner_ (&fRootMarker, NULL);
425 }
426 delete ((ChunkedArrayMarkerHook*)fRootMarker.fTextStoreHook);
427 }
428
429#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
430public:
431 using CollectLookupCacheElt = ChunkedArrayTextStore::CollectLookupCacheElt;
432 using CACHE_TYPE =
434
435public:
436 nonvirtual void ClearCache ()
437 {
438 fCache.ClearCache ();
439 }
440 nonvirtual CollectLookupCacheElt* LookupElement (const CollectLookupCacheElt::COMPARE_ITEM& item)
441 {
442 return fCache.LookupElement (item);
443 }
444 nonvirtual CollectLookupCacheElt* AddNew (const CollectLookupCacheElt::COMPARE_ITEM& item)
445 {
446 return fCache.AddNew (item);
447 }
448
449private:
450 CACHE_TYPE fCache;
451#endif
452
453public:
454 inline bool AllHackMarkers (const Marker* m)
455 {
456 AssertNotNull (m);
457 return (OurStuff (m)->fIsHackMarker and (OurStuff (m)->fFirstSubMarker == NULL or AllSubMarkersAreHackMarkerTrees (m)));
458 }
459
460public:
461 nonvirtual bool AllSubMarkersAreHackMarkerTrees (const Marker* m)
462 {
463 AssertNotNull (m);
464 Assert (OurStuff (m)->fIsHackMarker);
465
466 // Try unwinding tail recusion using explicit stack
467 size_t stackDepth = 0;
468 StackBuffer<Marker*> stack{0};
469
470 RoutineTop:
471 // Do in TWO loops rather than one simply because it is much faster to check the tree
472 // breadth-wise than depth-wise (no recursion/function call) so probably faster to check
473 // this way (assuming we get negative answers) even though we visit each node twice (assuming
474 // we end up with a yes
475 for (auto mi = OurStuff (m)->fFirstSubMarker; mi != NULL;) {
476 ChunkedArrayMarkerHook* misStuff = OurStuff (mi);
477 Assert (misStuff->fParent == m);
478 if (not misStuff->fIsHackMarker) {
479 return false;
480 }
481 mi = misStuff->fNextSubMarker;
482 }
483
484 auto mi = OurStuff (m)->fFirstSubMarker;
485 for (; mi != NULL; mi = OurStuff (mi)->fNextSubMarker) {
486 Assert (OurStuff (mi)->fIsHackMarker);
487 // Save args, then push new args
488 stack.GrowToSize (stackDepth + 1);
489 stack[stackDepth] = mi;
490 ++stackDepth;
491 m = mi; // bind formal arg
492 goto RoutineTop;
493 AfterCallPoint:
494 Assert (stackDepth > 0);
495 --stackDepth;
496 mi = stack[stackDepth];
497 }
498
499 if (stackDepth != 0) {
500 // pop the stack and continue in the loop
501 goto AfterCallPoint;
502 }
503 return (true);
504 }
505
506public:
507 nonvirtual void FreeHackTree (Marker* m)
508 {
509 AssertNotNull (m);
510 Assert (OurStuff (m)->fIsHackMarker);
511
512 // remove self from parent first, then blindly do tree delete...
513 Marker* parent = OurStuff (m)->fParent;
514 AssertNotNull (parent);
515 Marker* prevMarker = NULL;
516 for (auto mi = OurStuff (parent)->fFirstSubMarker; mi != NULL; (prevMarker = mi), (mi = OurStuff (mi)->fNextSubMarker)) {
517 Assert (OurStuff (mi)->fParent == parent);
518 if (mi == m) {
519 if (prevMarker == NULL) {
520 Assert (OurStuff (parent)->fFirstSubMarker == mi);
521 OurStuff (parent)->fFirstSubMarker = OurStuff (mi)->fNextSubMarker;
522 }
523 else {
524 Assert (OurStuff (prevMarker)->fNextSubMarker == mi);
525 OurStuff (prevMarker)->fNextSubMarker = OurStuff (mi)->fNextSubMarker;
526 }
527 OurStuff (mi)->fNextSubMarker = NULL;
528 FreeHackTree1 (m);
529 return;
530 }
531 }
532 Assert (false); // if we get here then we weren't in our parents linked list of children !!! BAD!!!
533 }
534
535 nonvirtual void FreeHackTree1 (Marker* m)
536 {
538 Assert (OurStuff (m)->fIsHackMarker);
539 }
540 for (Marker* mi = OurStuff (m)->fFirstSubMarker; mi != NULL;) {
541 Marker* nextMI = OurStuff (mi)->fNextSubMarker;
542 FreeHackTree1 (mi);
543 mi = nextMI;
544 }
545 AssertNotNull (m->fTextStoreHook);
546 bool isDeletedMarker = OurStuff (m)->fIsDeletedMarker;
547 delete ((ChunkedArrayMarkerHook*)m->fTextStoreHook);
548
549 if (isDeletedMarker) {
550 m->fTextStoreHook = NULL; // so not deleted twice by RemoveMarkers()
551 }
552 else {
554 m->fTextStoreHook = (ChunkedArrayMarkerHook*)666; // magic # so we know these are bad... Should never be referenced after this
555 }
556 delete m;
557#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
558 Assert (fTotalHackMarkersPresent > 0);
559 --fTotalHackMarkersPresent;
560#endif
561 }
562 }
563
564public:
565 Marker fRootMarker;
566
567#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
568public:
569 unsigned long fTotalMarkersPresent; // real markers - not including hack and root
570 unsigned long fTotalHackMarkersPresent;
571 unsigned long fPeakTotalMarkersPresent;
572 unsigned long fPeakHackMarkersPresent;
573 unsigned long fTotalHackMarkersAlloced; // total freed = fTotalHackMarkersAlloced - fTotalHackMarkersPresent
574#endif
575};
576
577inline ChunkedArrayMarkerOwnerHook* GetAMOH (const MarkerOwner* mo)
578{
579 RequireNotNull (mo);
580 RequireNotNull (mo->fTextStoreHook);
581 RequireNotNull (dynamic_cast<ChunkedArrayMarkerOwnerHook*> (mo->fTextStoreHook));
582 return dynamic_cast<ChunkedArrayMarkerOwnerHook*> (mo->fTextStoreHook);
583}
584inline ChunkedArrayMarkerOwnerHook* GetAMOH (ChunkedArrayMarkerHook* moh)
585{
586 RequireNotNull (moh);
587 return GetAMOH (moh->fOwner);
588}
589inline ChunkedArrayMarkerOwnerHook* GetAMOH (Marker* m)
590{
591 return GetAMOH (OurStuff (m));
592}
593
594/*
595 ********************************************************************************
596 ******************************** ChunkedArrayTextStore *************************
597 ********************************************************************************
598 */
599
600/*
601 * Preliminary Design ideas:
602 *
603 * Try to keep all chunks at least 1/2 full - except the last. Do invariant to assure
604 * this. Reason is to avoid memory waste - otherwise we might end up with a 4096byte block
605 * for each character!!!!
606 *
607 * On second thought - this isn't such a great idea. Might be better to have a separate
608 * coalesceall routine. Wind thru the list of chunks and if it even finds two adjacent that
609 * can be joined - then do so.
610 */
611ChunkedArrayTextStore::ChunkedArrayTextStore ()
612{
613 fTextStoreHook = new ChunkedArrayMarkerOwnerHook (this, 2);
614}
615
616ChunkedArrayTextStore::~ChunkedArrayTextStore ()
617{
618 Require (GetMarkerOwners ().size () == 1); // Really this should properly be checked in the TextStore::DTOR - and it is.
619 // But if this test fails, other tests within THIS DTOR will likely also fail. And
620 // those can be confusing. This diagnostic should clearly indicate to users that they've
621 // forgotten to remove some MarkerOwners - like Views or MarkerCovers, or ParagraphDatabases,
622 // etc.
623 //
624 // Note also - that since all markers must be removed for a given MarkerOwner before IT can be removed
625 // this constraint also implies we contain no markers!
626
627 delete fTextStoreHook;
628 fTextStoreHook = NULL;
629
630 // NB: We don't consider it an error to not remove all the text from the text editor.
631 for (size_t i = 0; i < fTextChunks.size (); i++) {
632 delete fTextChunks[i];
633 }
634}
635
636/*
637@METHOD: ChunkedArrayTextStore::ConstructNewTextStore
638@DESCRIPTION: <p>See @'TextStore::ConstructNewTextStore' ().</p>
639*/
640TextStore* ChunkedArrayTextStore::ConstructNewTextStore () const
641{
642 return new ChunkedArrayTextStore ();
643}
644
645void ChunkedArrayTextStore::AddMarkerOwner (MarkerOwner* owner)
646{
647 RequireNotNull (owner);
648 Require (owner->fTextStoreHook == NULL);
649 inherited::AddMarkerOwner (owner);
650 try {
651 owner->fTextStoreHook = new ChunkedArrayMarkerOwnerHook (owner, GetLength () + 2); // include ALL text, and ALL other markers - one more than
652 // the text imager does - not sure if any of that needed
653 // anymore??? LGP 941006 -yes probably is cuz we do >1
654 // length there - not so we get notified - that is
655 // already handled -but so we get autogrowing...
656 }
657 catch (...) {
658 inherited::RemoveMarkerOwner (owner);
659 throw;
660 }
661}
662
663void ChunkedArrayTextStore::RemoveMarkerOwner (MarkerOwner* owner)
664{
665 RequireNotNull (owner);
666 RequireNotNull (owner->fTextStoreHook);
667 RequireNotNull (dynamic_cast<ChunkedArrayMarkerOwnerHook*> (owner->fTextStoreHook));
668#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
669 {
670 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> (owner->fTextStoreHook);
671 Require (camoh->fTotalMarkersPresent == 0); // ALL MARKERS MUST BE REMOVED BEFORE EDITOR DESTROYED!!!!
672 // If this assertion ever triggers, then it may well be a Led client
673 // bug and not a Led-bug per-se. Check that all your markers have been
674 // removed!
675 //
676 // Look just below at 'markersWhichShouldHaveBeenDeleted' to see just what markers are left.
677 //
678 // See Led's FAQ#27 - http://www.sophists.com/Led/LedClassLib/ClassLibDocs/Recipes.html#27
680 if (camoh->fTotalMarkersPresent != 0) {
681 vector<Marker*> markersWhichShouldHaveBeenDeleted;
682 VectorMarkerSink tmp{&markersWhichShouldHaveBeenDeleted};
683 CollectAllMarkersInRangeInto (0, GetEnd () + 2, owner, tmp);
684 }
685 }
686 }
687#endif
688 delete owner->fTextStoreHook;
689 owner->fTextStoreHook = NULL;
690 inherited::RemoveMarkerOwner (owner);
691}
692
693void ChunkedArrayTextStore::CopyOut (size_t from, size_t count, Led_tChar* buffer) const noexcept
694{
695 // Note that it IS NOT an error to call CopyOut for multibyte characters and split them. This is one of the few
696 // API routines where that is so...
697 RequireNotNull (buffer);
698 Require (from >= 0);
699 Require (from + count <= GetEnd ()); // Be sure all Led_tChars requested fall in range
700
701 ChunkAndOffset chunkIdx = FindChunkIndex (from);
702 for (size_t bytesToGo = count; bytesToGo != 0;) {
703 Assert (chunkIdx.fChunk >= 0);
704 Assert (chunkIdx.fChunk < fTextChunks.size ()); // can call copyOut where this isn't true - but
705 // better not be copying any bytes then!!!
706 TextChunk* t = fTextChunks[chunkIdx.fChunk];
707 AssertNotNull (t);
708 size_t copyFromThisGuy = min (bytesToGo, t->GetLength () - (chunkIdx.fOffset));
709 t->CopyOut (chunkIdx.fOffset, copyFromThisGuy, &buffer[count - bytesToGo]);
710
711 // For next time through the loop
712 chunkIdx.fOffset = 0;
713 ++chunkIdx.fChunk;
714 bytesToGo -= copyFromThisGuy;
715 }
716}
717
718void ChunkedArrayTextStore::ReplaceWithoutUpdate (size_t from, size_t to, const Led_tChar* withWhat, size_t withWhatCount)
719{
720#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
721 for (vector<MarkerOwner*>::const_iterator i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
722 GetAMOH (*i)->ClearCache ();
723 }
724#endif
725 Assert (from <= to);
726 if (from != to or withWhatCount != 0) {
727 // THIS ISN't QUITE RIGHT - A GOOD APPROX HOWEVER...
728 // cuz we don't update markers properly yet... Close - but not quite, a replace
729 // is treated as a delete/insert - which isn't quite what we want...
730 /*
731 * Though the implication for updating markers is slightly different, for updating just
732 * the text, we can treat this as a delete, followed by an insert.
733 */
734
735 /*
736 * We could either do insert first or delete first.
737 *
738 * To assure the proper semantics for replace inside of markers however,
739 * we must at least update the markers for INSERTION before updating them
740 * for deletion. So this argues to do insert then delete.
741 *
742 * But - only paying attention to the speed of inserting the text,
743 * it is probably (maybe) faster to delete first and then insert.
744 * (actually if the delete causes a coalesce this may not be true).
745 *
746 * We could have both, by splitting the marker update code out from
747 * the text insert/delete routines. But right now I think the simplest
748 * (and most correct) thing todo is just do the insert first.
749 *
750 * <<
751 * WHOOPS! This rationale is WRONG - even this way, we would not get
752 * stuff inserted. Perhaps we really shouldn't. Unclear. See SPR#0212.
753 * For now, leave things as they were!!!
754 * >>
755 */
756 if (from != to) {
757 DeleteAfter_ (to - from, from);
758 }
759 if (withWhatCount != 0) {
760 InsertAfter_ (withWhat, withWhatCount, from);
761 }
762 }
763}
764
765void ChunkedArrayTextStore::InsertAfter_ (const Led_tChar* what, size_t howMany, size_t after)
766{
767 Assert (howMany > 0);
768 Assert (what != NULL);
769
770 Assert (howMany > 0);
771 Assert (what != NULL);
772
773 ChunkAndOffset chunkIdx = FindChunkIndex (after);
774
775 /*
776 * Make sure we have a good chunk to insert into - at least to begin with. This simplfies
777 * the code below which just tries to insert into the current chunk pointed to, and then
778 * inserts extra chunks as needed. This just removes a special case down there.
779 */
780 if (chunkIdx.fChunk >= fTextChunks.size ()) {
781 if (fTextChunks.size () == 0) {
782 (void)AtomicAddChunk (0);
783 }
784 else {
785 // If we are pointing past the end of a chunk - maybe we can just
786 // go back and insert inside the previous chunk. If not - THEN
787 // append a new one...
788 Assert (chunkIdx.fChunk >= 1);
789 TextChunk* preChunk = fTextChunks[chunkIdx.fChunk - 1];
790 if (preChunk->GetBytesCanAccommodate () == 0) {
791 (void)AtomicAddChunk (fTextChunks.size ());
792 }
793 else {
794 --chunkIdx.fChunk;
795 chunkIdx.fOffset = preChunk->GetLength ();
796 }
797 }
798 }
799 TextChunk* t = fTextChunks[chunkIdx.fChunk];
800 AssertNotNull (t);
801
802 /*
803 * Now add text and update bytesXFered as we go. On exceptions, we update fLength with ONLY
804 * AS MUCH AS WE ADDED. Similarly, we update the extents by only as much as we added.
805 */
806 size_t bytesXfered = 0;
807 try {
808 if (t->GetBytesCanAccommodate () >= howMany) {
809 /*
810 * We're happy - we can just insert into this chunk.
811 */
812 t->InsertAfter (what, howMany, chunkIdx.fOffset);
813 bytesXfered += howMany;
814 }
815 else {
816 /*
817 * Now things are MUCH more compicated. The stuff at the end of this
818 * chunk must be pushed forward into the following chunk - if
819 * it fits - and otherwise - we must build a new - following -
820 * chunk to accomodate it.
821 *
822 * Then - once we are truely appending to this chunk - we are
823 * all set.
824 */
825 {
826 if (chunkIdx.fChunk == fTextChunks.size () - 1) {
827 (void)AtomicAddChunk (chunkIdx.fChunk + 1);
828 }
829 TextChunk* followingChunk = fTextChunks[chunkIdx.fChunk + 1];
830 AssertNotNull (followingChunk);
831 size_t splitPoint = chunkIdx.fOffset;
832 size_t bytesToShift = t->GetLength () - (splitPoint);
833 const Led_tChar* peekAfter = (bytesToShift == 0) ? NULL : t->PeekAfter (splitPoint);
834 if (followingChunk->GetBytesCanAccommodate () >= bytesToShift) {
835 followingChunk->InsertAfter (peekAfter, bytesToShift, 0); // MUST PREPEND these chars
836 }
837 else {
838 // complexities within the complexities! If there isn't room in the following chunk
839 // then we do something to make room. A simple approach is to just cons up a new
840 // chunk and stick it between. This may be less than ideal - but it will do for now...
841 // LGP 941022
842 followingChunk = AtomicAddChunk (chunkIdx.fChunk + 1); // insert AFTER current item (before old following)...
843 followingChunk->InsertAfter (peekAfter, bytesToShift, 0);
844 }
845 t->DeleteAfter (bytesToShift, splitPoint); // shoundn't shuffle anything - just update length count
846 }
847
848 /*
849 * Ah - finally. Now that we've dispensed with the preliminaries - we can get on with the
850 * crux of our work. This is to append text to this chunk. And then keep looping. And
851 * as we loop - prepend to the following chunk ONLY IF IT COMPLETES THE TRANSFER,
852 * and otherwise - keep building new chunks, and inserting them.
853 */
854 size_t bytesToGo = howMany;
855 size_t copyFromThisGuy = min (bytesToGo, t->GetBytesCanAccommodate ());
856 t->InsertAfter (&what[bytesXfered], copyFromThisGuy, chunkIdx.fOffset);
857
858 bytesToGo -= copyFromThisGuy;
859 bytesXfered += copyFromThisGuy;
860
861 for (; bytesToGo != 0;) {
862 ++chunkIdx.fChunk;
863 if (chunkIdx.fChunk <= fTextChunks.size () - 1 and fTextChunks[chunkIdx.fChunk]->GetBytesCanAccommodate () >= bytesToGo) {
864 // Yippie - we're done. Just prepend here, and lets go...
865 t = fTextChunks[chunkIdx.fChunk];
866 t->InsertAfter (&what[bytesXfered], bytesToGo, 0);
867 bytesXfered += bytesToGo;
868 bytesToGo -= bytesToGo;
869 Assert (bytesToGo == 0);
870 Assert (bytesXfered == howMany);
871 }
872 else {
873 // Since we've filled the previous - and cannot fit in the following,
874 // we'll have to create another...
875 t = AtomicAddChunk (chunkIdx.fChunk);
876 AssertNotNull (t);
877 copyFromThisGuy = min (bytesToGo, t->GetBytesCanAccommodate ());
878 t->InsertAfter (&what[bytesXfered], copyFromThisGuy, 0);
879 bytesToGo -= copyFromThisGuy;
880 bytesXfered += copyFromThisGuy;
881 }
882 }
883 }
884 }
885 catch (...) {
886 fLength += bytesXfered;
887 AdjustMarkersForInsertAfter (after, bytesXfered);
888 throw;
889 }
890
891 Assert (howMany == bytesXfered);
892 fLength += bytesXfered;
893 AdjustMarkersForInsertAfter (after, bytesXfered);
894}
895
896void ChunkedArrayTextStore::DeleteAfter_ (size_t howMany, size_t after)
897{
898 Assert (after >= 0);
899 Assert ((after) + howMany <= GetLength ());
900 Assert (howMany > 0);
901
902 ChunkAndOffset chunkIdx = FindChunkIndex (after);
903 for (size_t bytesToGo = howMany; bytesToGo != 0;) {
904
905 TextChunk* t = fTextChunks[chunkIdx.fChunk];
906 AssertNotNull (t);
907
908 // Now try to insert what we can into this section...
909 size_t deleteFromThisGuy = min (bytesToGo, t->GetLength () - (chunkIdx.fOffset));
910 Assert (deleteFromThisGuy != 0);
911 t->DeleteAfter (deleteFromThisGuy, chunkIdx.fOffset);
912 bytesToGo -= deleteFromThisGuy;
913 fLength -= deleteFromThisGuy;
914
915 // For next time through the loop
916 chunkIdx.fOffset = 0;
917 chunkIdx.fChunk += 1;
918
919 // MUST DO SOMETHING TO COALECE BLOCKS
920 // Not a solution - but at LEAST we can eliminate ZERO LENGTH BLOCKS!!!
921 // SPR#0012
922
923 if (t->GetLength () == 0) {
924 chunkIdx.fChunk -= 1; // so we refer to same block after removeat
925 fTextChunks.erase (fTextChunks.begin () + chunkIdx.fChunk);
926 delete t;
927 }
928 }
929 AdjustMarkersForDeleteAfter (after, howMany);
930}
931
932void ChunkedArrayTextStore::AddMarker (Marker* marker, size_t lhs, size_t length, MarkerOwner* owner)
933{
934 RequireNotNull (marker);
935 RequireNotNull (owner);
936 Require (owner->PeekAtTextStore () == this);
937 Require (owner == this or IndexOf (GetMarkerOwners (), owner) != kBadIndex); // new Led 2.3 requirement - not strictly required internally yet - but it will be - LGP 980416
938 Require (marker->fTextStoreHook == NULL);
939 Require (lhs < 0x80000000); // not real test, just sanity check
940 Require (length < 0x80000000); // not real test, just sanity check
941 Require (lhs + length <= fLength + 1); // perhaps shouldn't require, but current implementation DOES require this
942 Invariant ();
943
944 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> (owner->fTextStoreHook);
945 AssertNotNull (camoh);
946
947#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
948 camoh->ClearCache ();
949#endif
950
951 marker->fTextStoreHook = new ChunkedArrayMarkerHook ();
952
953 //DON'T call - cuz asserts owner NON-NULL Assert (marker->GetOwner () == NULL);
954 SetMarkerOwner_ (marker, owner);
955
956 SetMarkerStart_ (marker, lhs);
957 SetMarkerLength_ (marker, length);
958
959 AddMarker1 (marker, &camoh->fRootMarker, true);
960
961#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
962 ++camoh->fTotalMarkersPresent;
963 camoh->fPeakTotalMarkersPresent = max (camoh->fPeakTotalMarkersPresent, camoh->fTotalMarkersPresent);
964#endif
965 Invariant ();
966}
967
968#ifndef qKeepTrackOfChildCountAndAvoidSomePossiblyAdds_
969#define qKeepTrackOfChildCountAndAvoidSomePossiblyAdds_ 1
970#endif
971
972void ChunkedArrayTextStore::AddMarker1 (Marker* marker, Marker* insideMarker, bool canAddHackMarkers)
973{
974 AssertNotNull (marker);
975 AssertNotNull (insideMarker);
976 Assert (marker != insideMarker);
977 AssertNotNull (marker->GetOwner ());
978 AssertNotNull (insideMarker->GetOwner ());
979 Assert (QUICK_Contains (*marker, *insideMarker));
980 Assert (OurStuff (marker)->fParent == NULL);
981 Assert (OurStuff (marker)->fNextSubMarker == NULL); // should be properly removed from sibling list - OK if we keep SUBMARKER list!
982
983 // NB: These are used throughout the loop, but are invariant throughout this function call
984 size_t markerStart = GetMarkerStart_ (marker);
985 size_t markerEnd = markerStart + GetMarkerLength_ (marker);
986
987 /*
988 * Somewhat tricky.
989 *
990 * The basic idea here is to build a tree of submarkers corresponding to
991 * the already existent Contains() relationship. The KEY FEATURE of the
992 * Contains() relationship - is that it remains INVARIANT under text insertions/
993 * deletions (and the marker updates they cause). This means the tree structure
994 * we build - remains valid with respect to that relationship. So - we can use
995 * the tree structure to find all items in a particular range without scanning
996 * all markers - just apx Log n of them (assuming we can keep the tree balanced,
997 * and all notes at most k-ary where k is constant).
998 */
999 Marker* specificInsideMarker = insideMarker;
1000Again:
1001#if qKeepTrackOfChildCountAndAvoidSomePossiblyAdds_
1002 size_t specificInsideMarkerChildCount = 0; // use in PossiblyAddHackMarkers optimization below
1003#endif
1004 Marker* prevMarker = NULL;
1005 for (Marker* curChild = OurStuff (specificInsideMarker)->fFirstSubMarker; curChild != NULL;
1006 (prevMarker = curChild), (curChild = OurStuff (curChild)->fNextSubMarker)) {
1007 Assert (marker != curChild);
1008
1009#if qKeepTrackOfChildCountAndAvoidSomePossiblyAdds_
1010 ++specificInsideMarkerChildCount;
1011#endif
1012
1013 /*
1014 * Important optimization. Check if the two markers are equal, and if so, then insert right here,
1015 * replacing the marker (by position) and pushing him down to be one of our new children.
1016 *
1017 * The reason this is important is if we have a very tall tree with lots of markers all at the same
1018 * position (e.g. 2500 zero length sentence extents in LVEJ) then inserting a new one could
1019 * involve walking a very long linked list all the way down to the leaf of the tree. Not wrong,
1020 * but not worth the long walk!
1021 *
1022 * Note - no good reason to not do this for OurStuff (marker)->fFirstSubMarker == NULL - except more
1023 * complex case - ignore for now - LGP 950525
1024 */
1025 size_t curChildStart = GetMarkerStart_ (curChild);
1026 size_t curChildEnd = curChildStart + GetMarkerLength_ (curChild);
1027 if (markerStart == curChildStart and markerEnd == curChildEnd and OurStuff (marker)->fFirstSubMarker == NULL) {
1028 Assert (QUICK_Contains (*marker, *curChild)); // we could have gone deeper in contains, but why bother?
1029
1030 /*
1031 * Set 'marker' up to point all the places curChild used to
1032 */
1033 Assert (OurStuff (marker)->fParent == NULL);
1034 OurStuff (marker)->fParent = OurStuff (curChild)->fParent;
1035
1036 Assert (OurStuff (marker)->fFirstSubMarker == NULL);
1037 OurStuff (marker)->fFirstSubMarker = OurStuff (curChild)->fFirstSubMarker;
1038
1039 Assert (OurStuff (marker)->fNextSubMarker == NULL);
1040 OurStuff (marker)->fNextSubMarker = OurStuff (curChild)->fNextSubMarker;
1041
1042 // Make those who used to point to 'curChild' now point to our new marker
1043 if (prevMarker == NULL) {
1044 Assert (OurStuff (marker)->fParent == specificInsideMarker);
1045 Assert (OurStuff (specificInsideMarker)->fFirstSubMarker == curChild);
1046 OurStuff (specificInsideMarker)->fFirstSubMarker = marker;
1047 }
1048 else {
1049 Assert (OurStuff (prevMarker)->fNextSubMarker == curChild);
1050 OurStuff (prevMarker)->fNextSubMarker = marker;
1051 }
1052
1053 // Now patch 'curChild' to now REALLY become OUR one and only child
1054 OurStuff (curChild)->fNextSubMarker = OurStuff (marker)->fFirstSubMarker;
1055 OurStuff (marker)->fFirstSubMarker = curChild;
1056 OurStuff (curChild)->fFirstSubMarker = NULL;
1057 OurStuff (curChild)->fParent = marker;
1058
1059 // Now walk all our children and adjust thier parent pointer - since they used to point
1060 // to 'curChild'
1061 for (Marker* mm = OurStuff (marker)->fFirstSubMarker; mm != NULL; mm = OurStuff (mm)->fNextSubMarker) {
1062 Assert (OurStuff (mm)->fParent == curChild or (mm == curChild));
1063 OurStuff (mm)->fParent = marker;
1064 }
1065
1066 goto Done;
1067 }
1068
1069 Assert (QUICK_Contains (*marker, *curChild) == QUICK_Contains (markerStart, markerEnd, curChildStart, curChildEnd));
1070 if (QUICK_Contains (markerStart, markerEnd, curChildStart, curChildEnd)) {
1071 specificInsideMarker = curChild;
1072 goto Again;
1073 }
1074 }
1075
1076#if qKeepTrackOfChildCountAndAvoidSomePossiblyAdds_
1077 if (specificInsideMarkerChildCount < kEnufChildrenToApplyHackMarkers) {
1078 canAddHackMarkers = false;
1079 }
1080#endif
1081
1082 /*
1083 * If we got here - then the marker we are adding was not strictly contained
1084 * inside any of our existing submarkers. We must just add it to our list - for
1085 * now - maybe later - adding hack markers to help create depth in the tree.
1086 */
1087 OurStuff (specificInsideMarker)->AddToChildList (marker);
1088 Assert (OurStuff (marker)->fParent == NULL);
1089 OurStuff (marker)->fParent = specificInsideMarker;
1090
1091Done:
1092 if (canAddHackMarkers) {
1093 PossiblyAddHackMarkers (specificInsideMarker);
1094 }
1095}
1096
1097void ChunkedArrayTextStore::PossiblyAddHackMarkers (Marker* insideMarker)
1098{
1099 /*
1100 * No point in introducing hack markers unless we have any chance of reducing the breadth, and no chance of
1101 * that if we don't have an inside marker at least two wide!!!
1102 */
1103 if (GetMarkerLength_ (insideMarker) >= 2 and OurStuff (insideMarker)->CountChildrenMoreThan (kEnufChildrenToApplyHackMarkers)) {
1104 size_t insideMarkerStart = GetMarkerStart_ (insideMarker);
1105 size_t insideMarkerEnd = insideMarkerStart + GetMarkerLength_ (insideMarker);
1106 size_t insideMarkerLength = insideMarkerEnd - insideMarkerStart;
1107
1108 /*
1109 * Add two hack markers splitting the current cell.
1110 */
1111 Marker* lhs = NULL;
1112 Marker* rhs = NULL;
1113
1114 try {
1115 lhs = AddHackMarkerHelper_ (insideMarker, insideMarkerStart, insideMarkerLength / 2);
1116 size_t rhsStart = insideMarkerStart + insideMarkerLength / 2;
1117 rhs = AddHackMarkerHelper_ (insideMarker, rhsStart, insideMarkerEnd - rhsStart);
1118
1119 // Now, walk through the list of markers, and see if any of the existing items can be move
1120 // into the hack-markers...
1121 Assert (OurStuff (insideMarker)->CountChildren () > 2);
1122 Marker* prevMarker = NULL;
1123 for (Marker* curMarker = OurStuff (insideMarker)->fFirstSubMarker; curMarker != NULL;) {
1124 Marker* nextMarker = OurStuff (curMarker)->fNextSubMarker;
1125 if (curMarker != lhs and curMarker != rhs) { // skip hack markers
1126 DISABLE_COMPILER_MSC_WARNING_START (6011)
1127 if (QUICK_Contains (*curMarker, *lhs)) {
1128 AssertNotNull (prevMarker); // cuz two hack markers come first
1129 // Now remove old, and re-call add marker
1130 OurStuff (prevMarker)->fNextSubMarker = OurStuff (curMarker)->fNextSubMarker;
1131 OurStuff (curMarker)->fNextSubMarker = NULL;
1132 Assert (OurStuff (curMarker)->fParent == insideMarker);
1133 OurStuff (curMarker)->fParent = NULL;
1134 AddMarker1 (curMarker, lhs, false);
1135 // don't reset prev, but make cur now be next
1136 curMarker = nextMarker;
1137 continue;
1138 }
1139 else if (QUICK_Contains (*curMarker, *rhs)) {
1140 AssertNotNull (prevMarker); // cuz two hack markers come first
1141 // Now remove old, and re-call add marker
1142 OurStuff (prevMarker)->fNextSubMarker = OurStuff (curMarker)->fNextSubMarker;
1143 OurStuff (curMarker)->fNextSubMarker = NULL;
1144 Assert (OurStuff (curMarker)->fParent == insideMarker);
1145 OurStuff (curMarker)->fParent = NULL;
1146 AddMarker1 (curMarker, rhs, false);
1147 // don't reset prev, but make cur now be next
1148 curMarker = nextMarker;
1149 continue;
1150 }
1151 DISABLE_COMPILER_MSC_WARNING_END (6011)
1152 }
1153 prevMarker = curMarker;
1154 curMarker = nextMarker;
1155 }
1156 LoseIfUselessHackMarker (lhs);
1157 LoseIfUselessHackMarker (rhs);
1158 }
1159 catch (...) {
1160 // MUST BE MORE CAREFUL HOW WE DELETE HACK MARKERS!!!
1161 // THIS CODE IS CURRENTLY UNSAFE
1162 Assert (false); // so when debugging at least we get a hint as to what went wrong... LGP 960313
1163 // delete lhs;
1164 // delete rhs;
1165
1166 // NOTE: WE DON'T RE-THROW here!!!
1167 // Simply eat the exception. No big deal if no memory to add a hack-marker.
1168 }
1169 }
1170}
1171
1172void ChunkedArrayTextStore::RemoveMarkers (Marker* const markerArray[], size_t markerCount)
1173{
1174#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
1175 // could only do this for affected markers, but we want to avoid redundant clearcache calls - and this is simpler...
1176 // Probably no real performance harm...
1177 for (vector<MarkerOwner*>::const_iterator i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
1178 GetAMOH (*i)->ClearCache ();
1179 }
1180#endif
1181 Assert (markerCount == 0 or markerArray != NULL);
1182
1183 /*
1184 * This is a fairly primitive implementation, and I can easily imagine doing much better.
1185 * But this is so simple, and runs at least 20x faster than the old code (for 500K delete
1186 * all text test case). It seems fast enough for now - LGP 950416.
1187 */
1188 for (size_t i = 0; i < markerCount; ++i) {
1189 Assert (not OurStuff (markerArray[i])->fIsHackMarker);
1190 OurStuff (markerArray[i])->fIsHackMarker = true;
1191 OurStuff (markerArray[i])->fIsDeletedMarker = true;
1192#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
1193 Assert (GetAMOH (markerArray[i])->fTotalMarkersPresent >= 1);
1194 GetAMOH (markerArray[i])->fTotalMarkersPresent -= 1;
1195#endif
1196 }
1197
1198 for (size_t i = 0; i < markerCount; ++i) {
1199 Marker* marker = markerArray[i];
1200 if (marker->fTextStoreHook != NULL) { // if its NULL - that means its already been destroyed as a
1201 // hack marker in a previous lap through the loop
1202 Assert (OurStuff (markerArray[i])->fIsHackMarker);
1203 RemoveMarker1 (marker);
1204 SetMarkerOwner_ (marker, NULL);
1205 AssertNotNull (marker->fTextStoreHook);
1206 delete ((ChunkedArrayMarkerHook*)marker->fTextStoreHook);
1207 marker->fTextStoreHook = NULL; // set to NULL so if we encounter it again, we won't try to delete it
1208 }
1209 }
1210}
1211
1212void ChunkedArrayTextStore::PreRemoveMarker (Marker* marker)
1213{
1214 RequireNotNull (marker);
1215 Require (not OurStuff (marker)->fIsPreRemoved);
1216 OurStuff (marker)->fIsPreRemoved = true;
1217}
1218
1219void ChunkedArrayTextStore::RemoveMarker1 (Marker* marker)
1220{
1221 AssertNotNull (marker);
1222 AssertNotNull (OurStuff (marker)->fParent);
1223
1224 /*
1225 * In order to remove an element from a tree, we must simply find out parent, and our
1226 * leftmost sibling, in order to remove ourselves.
1227 *
1228 * However, the other big thing todo is that each of our children must get redistributed
1229 * through the tree.
1230 */
1231 Marker* parent = OurStuff (marker)->fParent;
1232 Marker* ourLeftSibling = OurStuff (parent)->fFirstSubMarker;
1233 if (ourLeftSibling == marker) {
1234 ourLeftSibling = NULL;
1235 }
1236 else {
1237 AssertNotNull (ourLeftSibling);
1238 for (; OurStuff (ourLeftSibling)->fNextSubMarker != marker; ourLeftSibling = OurStuff (ourLeftSibling)->fNextSubMarker) {
1239 AssertNotNull (ourLeftSibling); // cuz we must be in our parents list someplace before the end!
1240 }
1241 }
1242 Assert (ourLeftSibling == NULL or OurStuff (ourLeftSibling)->fNextSubMarker == marker);
1243
1244 /*
1245 * Good. At this point we have our parent and our previous marker, so we can remove ourselves
1246 * easily. But first some hair.
1247 */
1248 if (ourLeftSibling == NULL) {
1249 Assert (OurStuff (parent)->fFirstSubMarker == marker);
1250 OurStuff (parent)->fFirstSubMarker = OurStuff (marker)->fNextSubMarker;
1251 }
1252 else {
1253 Assert (OurStuff (ourLeftSibling)->fNextSubMarker == marker);
1254 OurStuff (ourLeftSibling)->fNextSubMarker = OurStuff (marker)->fNextSubMarker;
1255 }
1256 OurStuff (marker)->fNextSubMarker = NULL;
1257 Assert (OurStuff (marker)->fParent == parent);
1258 OurStuff (marker)->fParent = NULL;
1259
1260 /*
1261 * Now - if that marker had any submarkers - those must all be moved up to the
1262 * parent. This should probably be done in such a way as to redistribute them -
1263 * but well do it the quick way for now...
1264 */
1265 size_t moveCount = 0;
1266 for (Marker* markerToMove = OurStuff (marker)->fFirstSubMarker; markerToMove != NULL;) {
1267 // first find who will be next marker in linked list since the below add will
1268 // blow our next ptr away
1269 Marker* nextMarkerToMove = OurStuff (markerToMove)->fNextSubMarker;
1270 OurStuff (markerToMove)->fNextSubMarker = NULL;
1271 OurStuff (markerToMove)->fParent = NULL;
1272 AddMarker1 (markerToMove, parent, false);
1273 markerToMove = nextMarkerToMove;
1274 ++moveCount;
1275 }
1276 if (moveCount > 1) {
1277 PossiblyAddHackMarkers (parent);
1278 }
1279
1280 /*
1281 * Now check and see if the removeall of from parent has created any empty hack trees.
1282 *
1283 * <NOTE - THIS CAN PROBABLY BE DONE BETTER - WE CAN CHECK BASED ON IF WE RMEOVED/MOVED HACK MARKJERS AND AVOID THIS TEST>
1284 * LGP 950525
1285 */
1286 for (Marker* hmi = OurStuff (parent)->fFirstSubMarker; hmi != NULL;) {
1287 ChunkedArrayMarkerHook* hmiStuff = OurStuff (hmi);
1288 Marker* nexthmi = hmiStuff->fNextSubMarker; // save this away before FreeHackTree call so we don't lose anything!
1289 if (OurStuff (hmi)->fIsHackMarker) {
1290 if (AllHackMarkers (hmi)) {
1291 GetAMOH (hmi)->FreeHackTree (hmi);
1292 }
1293 else {
1294 LoseIfUselessHackMarker (hmi);
1295 }
1296 }
1297 hmi = nexthmi;
1298 }
1299
1300 // FIX FOR LONGSTANDING BUG CLEANUPING HACKMARKERS???
1301 {
1302 // really must do while loop walking UP parent tree
1303 for (Marker* hmi = parent; OurStuff (hmi)->fIsHackMarker;) {
1304 Marker* nextUp = OurStuff (hmi)->fParent;
1305 if (AllHackMarkers (hmi)) {
1306 AssertNotNull (nextUp); // cuz top marker is fRootMarker - and thats not a hack marker!
1307 GetAMOH (hmi)->FreeHackTree (hmi);
1308 hmi = nextUp;
1309 }
1310 else {
1311 break;
1312 }
1313 }
1314 }
1315}
1316
1317Marker* ChunkedArrayTextStore::AddHackMarkerHelper_ (Marker* insideMarker, size_t start, size_t length)
1318{
1319 HackMarker* marker = new HackMarker ();
1320
1321 AssertNotNull (insideMarker);
1322
1323 try {
1324 Assert (marker->fTextStoreHook == NULL);
1325 marker->fTextStoreHook = new ChunkedArrayMarkerHook ();
1326 Assert (OurStuff (marker)->fParent == NULL);
1327 OurStuff (marker)->fParent = insideMarker;
1328 SetMarkerOwner_ (marker, OurStuff (insideMarker)->fOwner); // owner must be same as that of 'insideMarker' - so that all the caching/tree/stats etc kept coherent...
1329 SetMarkerStart_ (marker, start);
1330 SetMarkerLength_ (marker, length);
1331 OurStuff (insideMarker)->AddToChildList (marker);
1332
1333 Assert (not OurStuff (marker)->fIsHackMarker);
1334 OurStuff (marker)->fIsHackMarker = true;
1335#if qStroika_Frameworks_Led_KeepChunkedArrayStatistics
1336 ++GetAMOH (insideMarker)->fTotalHackMarkersAlloced;
1337 ++GetAMOH (insideMarker)->fTotalHackMarkersPresent;
1338 GetAMOH (insideMarker)->fPeakHackMarkersPresent =
1339 max (GetAMOH (insideMarker)->fPeakHackMarkersPresent, GetAMOH (insideMarker)->fTotalHackMarkersPresent);
1340#endif
1341 }
1342 catch (...) {
1343 // this is safe cuz only point we can throw is 'new ChunkedArrayMarkerHook ()', and at that point
1344 // nobody refers to our marker.
1345 delete marker;
1346 throw;
1347 }
1348 return (marker);
1349}
1350
1351void ChunkedArrayTextStore::LoseIfUselessHackMarker (Marker* potentiallyUselessHackMarker)
1352{
1353 AssertNotNull (potentiallyUselessHackMarker);
1354 Assert (OurStuff (potentiallyUselessHackMarker)->fIsHackMarker);
1355
1356 Marker* firstChild = OurStuff (potentiallyUselessHackMarker)->fFirstSubMarker;
1357
1358 bool truelyUseless = (firstChild == NULL) or (OurStuff (firstChild)->fNextSubMarker == NULL);
1359
1360 /*
1361 * Any hack marker with zero or one children is serving no purpose. So remove them.
1362 *
1363 * Here we first check for the harder case of one child. In that case we must
1364 * move the markers children up into our parent right where we are.
1365 */
1366 if (firstChild != NULL and OurStuff (firstChild)->fNextSubMarker == NULL) {
1367 Assert (truelyUseless);
1368 Marker* goodParent = OurStuff (potentiallyUselessHackMarker)->fParent;
1369 Assert (OurStuff (firstChild)->fParent == potentiallyUselessHackMarker);
1370 OurStuff (firstChild)->fParent = goodParent;
1371 OurStuff (firstChild)->fNextSubMarker = OurStuff (goodParent)->fFirstSubMarker;
1372 OurStuff (goodParent)->fFirstSubMarker = firstChild;
1373 OurStuff (potentiallyUselessHackMarker)->fFirstSubMarker = NULL; // avoid deleting subtree in FreeHackTree () below
1374 }
1375
1376 // Now we are left with a truelyUseless marker, with no children, so just remove it from parent, and dispose of it.
1377 if (truelyUseless) {
1378 GetAMOH (potentiallyUselessHackMarker)->FreeHackTree (potentiallyUselessHackMarker);
1379 }
1380}
1381
1382void ChunkedArrayTextStore::SetMarkerRange (Marker* marker, size_t start, size_t end) noexcept
1383{
1384 RequireNotNull (marker);
1385#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
1386 GetAMOH (marker)->ClearCache ();
1387#endif
1388 /*
1389 * NB: There is still alot of room to improve this routine's efficency, by
1390 * being more clever about how we move the marker and its children when we no
1391 * longer fit. If we are growing marker - then the children still fit, and can be
1392 * moved with the marker. If they DON'T fit, we can move then directly up to our parent,
1393 * and then move the marker. And we can inline make decisions here about whether to
1394 * add/remove hack markers, and where (maybe replace ourselves with a hack marker instead
1395 * of moving children up to parent? - yes - probably a VERY good idea!)
1396 *
1397 * These optimizations can - and probably will - wait til after the 1.0 release - LGP 950527.
1398 *
1399 */
1400 Assert (start >= 0);
1401 Assert (end >= 0);
1402 Assert (start <= end);
1403 AssertNotNull (marker);
1404
1405 size_t len = end - start;
1406
1407 // changing the start, or end may force a re-ordering...
1408 if (GetMarkerStart_ (marker) != start or GetMarkerLength_ (marker) != len) {
1409 /*
1410 * If this marker is still contained in its parent - no need to remove it.
1411 * Furthermore - if all its children remain in it - no need to move them up.
1412 * But the worst case is - remove marker - and re-add it.
1413 */
1414 Marker* parent = OurStuff (marker)->fParent;
1415 AssertNotNull (parent);
1416 Assert (QUICK_Contains (*marker, *parent));
1417 if (Contains (start, end, *parent)) {
1419 // before we re-adjust anything. - make sure all is well...
1420 for (Marker* mi = OurStuff (marker)->fFirstSubMarker; mi != NULL; mi = OurStuff (mi)->fNextSubMarker) {
1421 Assert (QUICK_Contains (*mi, *marker));
1422 }
1423 }
1424
1425 SetMarkerStart_ (marker, start);
1426 SetMarkerLength_ (marker, len);
1427
1428 /*
1429 * Now check each child - and if it is still contained in the new range - leave it -
1430 * otherwise - remove it, and move it to the parent.
1431 */
1432 Marker* prevMarker = NULL;
1433 for (Marker* mi = OurStuff (marker)->fFirstSubMarker; mi != NULL;) {
1434 Marker* nextMarker = OurStuff (mi)->fNextSubMarker;
1435 if (not QUICK_Contains (*mi, *marker)) { // cuz we just patched our start/size
1436 if (prevMarker == NULL) {
1437 Assert (OurStuff (marker)->fFirstSubMarker == mi);
1438 OurStuff (marker)->fFirstSubMarker = OurStuff (mi)->fNextSubMarker;
1439 }
1440 else {
1441 Assert (OurStuff (prevMarker)->fNextSubMarker == mi);
1442 OurStuff (prevMarker)->fNextSubMarker = nextMarker;
1443 }
1444 OurStuff (mi)->fNextSubMarker = NULL;
1445 OurStuff (mi)->fParent = NULL;
1446 AddMarker1 (mi, parent, false); // don't bother with adding hack markers - possibly might help but unlikely since we are going
1447 // into same list we started in (though possibly with different size)
1448 }
1449 else {
1450 prevMarker = mi;
1451 }
1452 mi = nextMarker;
1453 }
1454 }
1455 else {
1456 MarkerOwner* owner = marker->GetOwner ();
1457 RemoveMarker (marker);
1458 AddMarker (marker, start, len, owner);
1459 }
1460 }
1461}
1462
1463struct StackContext {
1464 const Marker* saved_belowHere;
1465 Marker* saved_mi;
1466};
1467void ChunkedArrayTextStore::CollectAllMarkersInRangeInto (size_t from, size_t to, const MarkerOwner* owner, MarkerSink& output) const
1468{
1469 RequireNotNull (owner); // though it can be TextStore::kAnyMarkerOwner.
1470 Require (from <= to);
1471
1472 /*
1473 * Please pardon the complexity of this routine. It was once written simply and clearly as a recursive function.
1474 * But it was a central bottleneck to Led's speed. So the recursion was unwound, and changed to use an
1475 * explicit, inline stack -- purely for speed.
1476 *
1477 * Similarly for this LRUCache code (SPR#0652).
1478 */
1479 if (owner == kAnyMarkerOwner) {
1480 for (vector<MarkerOwner*>::const_iterator i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
1481 CollectAllMarkersInRangeInto_Helper_MO (from, to, *i, output);
1482 }
1483 }
1484 else {
1485#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
1486 ChunkedArrayMarkerOwnerHook* camoh = GetAMOH (owner);
1487 CollectLookupCacheElt* cacheItem = camoh->LookupElement (CollectLookupCacheElt::COMPARE_ITEM (from, to));
1488 if (cacheItem != NULL) {
1489 for (vector<Marker*>::const_iterator i = cacheItem->fMarkers.begin (); i != cacheItem->fMarkers.end (); ++i) {
1490 if (not OurStuff (*i)->fIsPreRemoved) {
1491 output.Append (*i);
1492 }
1493 }
1494 return;
1495 }
1496 if (cacheItem == NULL and (to - from) <= 3) { // only try to cache small guys (but at least size 3 cuz of _Surrounding() calls...
1497 cacheItem = camoh->AddNew (CollectLookupCacheElt::COMPARE_ITEM (from, to));
1498 cacheItem->fFrom = from;
1499 cacheItem->fTo = to;
1500 cacheItem->fMarkers.clear ();
1501 CollectAllMarkersInRangeInto_Helper_MO (from, to, owner, output, cacheItem);
1502 return;
1503 }
1504#endif
1505 CollectAllMarkersInRangeInto_Helper_MO (from, to, owner, output);
1506 }
1507}
1508
1509void ChunkedArrayTextStore::CollectAllMarkersInRangeInto_Helper_MO (size_t from, size_t to, const MarkerOwner* owner, MarkerSink& output
1510#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
1511 ,
1512 CollectLookupCacheElt* fillingCache
1513#endif
1514) const
1515{
1516 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> (owner->fTextStoreHook);
1517 AssertNotNull (camoh);
1518 const Marker* belowHere = &camoh->fRootMarker;
1519 size_t stackDepth = 0;
1521
1522RoutineTop:
1523 AssertNotNull (belowHere);
1524 Marker* mi = OurStuff (belowHere)->fFirstSubMarker;
1525 ChunkedArrayMarkerHook* mio = mi == NULL ? NULL : OurStuff (mi); // be SURE to always keep 'mio' in sync with 'mi'
1526 for (; mi != NULL; (mi = mio->fNextSubMarker), (mio = mi == NULL ? NULL : OurStuff (mi))) {
1527 AssertNotNull (mi);
1528 AssertNotNull (mio);
1529 Assert (mio == OurStuff (mi));
1530 Assert (QUICK_Overlap (mio, from, to) == QUICK_Overlap (*mi, from, to));
1531 if (QUICK_Overlap (mio, from, to)) {
1532 if (not mio->fIsHackMarker and not mio->fIsPreRemoved) {
1533#if qStroika_Frameworks_Led_UseLRUCacheForRecentlyLookedUpMarkers
1534 if (fillingCache != NULL) {
1535 fillingCache->fMarkers.push_back (mi);
1536 }
1537#endif
1538 output.Append (mi);
1539 }
1540 // Save args, then push new args
1541 stack.GrowToSize (stackDepth + 1);
1542 stack[stackDepth].saved_belowHere = belowHere;
1543 stack[stackDepth].saved_mi = mi;
1544 ++stackDepth;
1545 belowHere = mi; // bind formal arg
1546 goto RoutineTop;
1547 AfterCallPoint:
1548 Assert (stackDepth > 0);
1549 --stackDepth;
1550 belowHere = stack[stackDepth].saved_belowHere;
1551 mi = stack[stackDepth].saved_mi;
1552 AssertNotNull (mi);
1553 mio = OurStuff (mi);
1554 }
1555 }
1556 if (stackDepth != 0) {
1557 // pop the stack and continue in the loop
1558 goto AfterCallPoint;
1559 }
1560}
1561
1562ChunkedArrayTextStore::ChunkAndOffset ChunkedArrayTextStore::FindChunkIndex_ (size_t charPos) const
1563{
1564 Assert (charPos >= 0);
1565 Assert (charPos <= GetEnd ());
1566 size_t totalChunks = fTextChunks.size ();
1567 size_t bytePosSoFar = 0;
1568
1569 /*
1570 * A note of caution about the cahing of the indexes here. We currently never bother
1571 * invalidating the fCachedChunkIndexesOffset/fCachedChunkIndex because the only
1572 * way we can get in trouble with them is if somebody inserts stuff or deletes stuff
1573 * BEFORE our cached indexes. But all the code that does the insert/delete calls this
1574 * routine first, and only modifies stuff AFTER the ChunkAndOffset () we return - this
1575 * not affecting our cache (since we never return an answer < our cached value).
1576 *
1577 * This is a somewhat fragile strategy - and care should be taken in modifying code
1578 * which might change this assumption. Of course - if there is a change that breaks this-
1579 * all that needs to be done is to call the invalidate routine.
1580 */
1581 size_t startAt = 0;
1582 if (charPos >= fCachedChunkIndexesOffset) {
1583 startAt = fCachedChunkIndex;
1584 Assert (fCachedChunkIndexesOffset >= 0);
1585 bytePosSoFar = fCachedChunkIndexesOffset;
1586 }
1587 for (size_t i = startAt; i < totalChunks; ++i) {
1588 AssertNotNull (fTextChunks[i]);
1589 size_t curChunkSize = fTextChunks[i]->GetLength ();
1590 if (bytePosSoFar + curChunkSize > charPos) {
1591 Assert (charPos >= bytePosSoFar + 0);
1592 // set new cache value
1593 fCachedChunkIndex = i;
1594 fCachedChunkIndexesOffset = bytePosSoFar + 0;
1595 return (ChunkAndOffset (i, charPos - bytePosSoFar));
1596 }
1597 bytePosSoFar += curChunkSize;
1598 }
1599 return (ChunkAndOffset (totalChunks, 0)); // we might be appending - so this is OK.. Refer to non-existent chunk
1600}
1601
1602void ChunkedArrayTextStore::AdjustMarkersForInsertAfter (size_t after, size_t howMany)
1603{
1604 for (auto i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
1605 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> ((*i)->fTextStoreHook);
1606 AssertNotNull (camoh);
1607 AdjustMarkersForInsertAfter1 (after, howMany, &camoh->fRootMarker);
1608 }
1609}
1610
1611void ChunkedArrayTextStore::AdjustMarkersForInsertAfter1 (size_t after, size_t howMany, Marker* startAt)
1612{
1613 AssertNotNull (startAt);
1614
1615 // Try unwinding tail recusion using explicit stack
1616 size_t stackDepth = 0;
1617 StackBuffer<Marker*> stack{0};
1618
1619 Marker* mi = NULL; // declared up here instead of down below to avoid MSVC 2.1 compiler bug.
1620 // LGP 950527
1621
1622RoutineTop:
1623 /*
1624 * Adjust THIS marker.
1625 */
1626 {
1627 size_t start = GetMarkerStart_ (startAt);
1628 size_t len = GetMarkerLength_ (startAt);
1629 size_t end = start + len;
1630 if (after < start) {
1631 SetMarkerStart_ (startAt, start + howMany);
1632 }
1633 else if (after >= start and after < end) {
1634 SetMarkerLength_ (startAt, len + howMany);
1635 }
1636 else {
1637 Assert (after >= end); // then we don't need to adjust any of our children either!
1638 if (stackDepth == 0) {
1639 return;
1640 }
1641 else {
1642 goto AfterCallPoint;
1643 }
1644 }
1645 }
1646
1647 /*
1648 * Adjust its children...
1649 */
1650 for (mi = OurStuff (startAt)->fFirstSubMarker; mi != NULL; mi = OurStuff (mi)->fNextSubMarker) {
1651 // Save args, then push new args
1652 stack.GrowToSize (stackDepth + 1);
1653 stack[stackDepth] = mi;
1654 ++stackDepth;
1655 startAt = mi; // bind formal arg
1656 goto RoutineTop;
1657 AfterCallPoint:
1658 Assert (stackDepth > 0);
1659 --stackDepth;
1660 mi = stack[stackDepth];
1661 }
1662 if (stackDepth != 0) {
1663 // pop the stack and continue in the loop
1664 goto AfterCallPoint;
1665 }
1666}
1667
1668void ChunkedArrayTextStore::AdjustMarkersForDeleteAfter (size_t after, size_t howMany)
1669{
1670 for (auto i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
1671 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> ((*i)->fTextStoreHook);
1672 AssertNotNull (camoh);
1673 AdjustMarkersForDeleteAfter1 (after, howMany, &camoh->fRootMarker);
1674 }
1675}
1676
1677void ChunkedArrayTextStore::AdjustMarkersForDeleteAfter1 (size_t after, size_t howMany, Marker* startAt)
1678{
1679 AssertNotNull (startAt);
1680
1681 // Try unwinding tail recusion using explicit stack
1682 size_t stackDepth = 0;
1683 StackBuffer<Marker*> stack{0};
1684
1685 Marker* mi = NULL; // declared up here instead of down below to avoid MSVC 2.1 compiler bug.
1686// LGP 950527
1687RoutineTop:
1688 /*
1689 * Adjust THIS marker.
1690 */
1691 {
1692 size_t start = GetMarkerStart_ (startAt);
1693 size_t len = GetMarkerLength_ (startAt);
1694 size_t end = start + len;
1695 if (after < start) {
1696 // size_t newStart = start;
1697 if (howMany + after <= start) {
1698 Assert (start >= howMany + 0);
1699 SetMarkerStart_ (startAt, start - howMany);
1700 }
1701 else {
1702 Assert (howMany > (start - after));
1703 size_t deleteNCharsOffFront = howMany - (start - after);
1704 size_t moveFront = howMany - deleteNCharsOffFront;
1705 SetMarkerStart_ (startAt, start - moveFront);
1706 /*
1707 * Note when the whole extent is deleted - we simply pin the size to zero.
1708 */
1709 SetMarkerLength_ (startAt, (len > deleteNCharsOffFront) ? (len - deleteNCharsOffFront) : 0);
1710 }
1711 }
1712 else if (after >= start and after < end) {
1713 size_t newEnd = end;
1714 if (end - after < howMany) {
1715 newEnd = after;
1716 }
1717 else {
1718 newEnd -= howMany;
1719 }
1720 Assert (newEnd >= start);
1721 size_t newLen = newEnd - start;
1722 SetMarkerLength_ (startAt, newLen);
1723 }
1724 else {
1725 Assert (after >= end); // then we don't need to adjust any of our children either!
1726 if (stackDepth == 0) {
1727 return;
1728 }
1729 else {
1730 goto AfterCallPoint;
1731 }
1732 }
1733 }
1734
1735 /*
1736 * Adjust its children...
1737 */
1738 for (mi = OurStuff (startAt)->fFirstSubMarker; mi != NULL; mi = OurStuff (mi)->fNextSubMarker) {
1739 // Save args, then push new args
1740 stack.GrowToSize (stackDepth + 1);
1741 stack[stackDepth] = mi;
1742 ++stackDepth;
1743 startAt = mi; // bind formal arg
1744 goto RoutineTop;
1745 AfterCallPoint:
1746 Assert (stackDepth > 0);
1747 --stackDepth;
1748 mi = stack[stackDepth];
1749 }
1750
1751 if (stackDepth != 0) {
1752 // pop the stack and continue in the loop
1753 goto AfterCallPoint;
1754 }
1755}
1756
1757bool ChunkedArrayTextStore::AllSubMarkersAreHackMarkerTrees (const Marker* m)
1758{
1759 AssertNotNull (m);
1760 Assert (OurStuff (m)->fIsHackMarker);
1761
1762 // Try unwinding tail recusion using explicit stack
1763 size_t stackDepth = 0;
1764 StackBuffer<Marker*> stack{0};
1765
1766RoutineTop:
1767 // Do in TWO loops rather than one simply because it is much faster to check the tree
1768 // breadth-wise than depth-wise (no recursion/function call) so probably faster to check
1769 // this way (assuming we get negative answers) even though we visit each node twice (assuming
1770 // we end up with a yes
1771 for (Marker* mi = OurStuff (m)->fFirstSubMarker; mi != NULL;) {
1772 ChunkedArrayMarkerHook* misStuff = OurStuff (mi);
1773 Assert (misStuff->fParent == m);
1774 if (not misStuff->fIsHackMarker) {
1775 return false;
1776 }
1777 mi = misStuff->fNextSubMarker;
1778 }
1779
1780 Marker* mi = OurStuff (m)->fFirstSubMarker;
1781 for (; mi != NULL; mi = OurStuff (mi)->fNextSubMarker) {
1782 Assert (OurStuff (mi)->fIsHackMarker);
1783 // Save args, then push new args
1784 stack.GrowToSize (stackDepth + 1);
1785 stack[stackDepth] = mi;
1786 ++stackDepth;
1787 m = mi; // bind formal arg
1788 goto RoutineTop;
1789 AfterCallPoint:
1790 Assert (stackDepth > 0);
1791 --stackDepth;
1792 mi = stack[stackDepth];
1793 }
1794
1795 if (stackDepth != 0) {
1796 // pop the stack and continue in the loop
1797 goto AfterCallPoint;
1798 }
1799 return (true);
1800}
1801
1802#if qStroika_Foundation_Debug_AssertionsChecked
1803void ChunkedArrayTextStore::Invariant_ () const
1804{
1805 TextStore::Invariant_ ();
1806 for (auto i = GetMarkerOwners ().begin (); i != GetMarkerOwners ().end (); ++i) {
1807 ChunkedArrayMarkerOwnerHook* camoh = dynamic_cast<ChunkedArrayMarkerOwnerHook*> ((*i)->fTextStoreHook);
1808 AssertNotNull (camoh);
1809 WalkSubTreeAndCheckInvariants (&camoh->fRootMarker);
1810 }
1811}
1812#endif
1813
1814#if qStroika_Foundation_Debug_AssertionsChecked
1815void ChunkedArrayTextStore::WalkSubTreeAndCheckInvariants (const Marker* m) const
1816{
1817 AssertNotNull (m);
1818 AssertNotNull (OurStuff (m));
1819 for (const Marker* subMarker = OurStuff (m)->fFirstSubMarker; subMarker != NULL; subMarker = OurStuff (subMarker)->fNextSubMarker) {
1820 AssertNotNull (subMarker);
1821 AssertNotNull (OurStuff (subMarker));
1822 Assert (OurStuff (subMarker)->fParent == m);
1823 Assert (QUICK_Contains (*subMarker, *m));
1824 Assert (not OurStuff (subMarker)->fIsHackMarker or OurStuff (subMarker)->fFirstSubMarker != NULL); // leaf hacks should be deleted!
1825 WalkSubTreeAndCheckInvariants (subMarker);
1826 }
1827}
1828#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define EnsureNotNull(p)
Definition Assertions.h:343
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertMember(p, c)
Definition Assertions.h:315
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
LRUCache implements a simple least-recently-used caching strategy, with optional hashing (of keys) to...
Definition LRUCache.h:224
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...