sq3: show SQLite error messages on stderr by default
[iv.d.git] / sdpy / region.d
blob0c86d1d2e6fbfdbd99bc4c0541a4c26d08b32ca1
1 /*
2 * Pixel Graphics Library
3 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
4 * Understanding is not required. Only obedience.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, version 3 of the License ONLY.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module iv.sdpy.region /*is aliced*/;
19 import iv.alice;
22 // ////////////////////////////////////////////////////////////////////////// //
23 // regions are copy-on-write shared, yay
24 struct Region {
25 alias SpanType = ushort; // you probably will never need this, but...
27 /// combine operation for region combiner %-)
28 enum CombineOp {
29 Or, /// logic or
30 And, /// logic and
31 Xor, /// logic exclusive or
32 Cut, /// cut solid parts
33 NCut, /// cut empty parts
36 @property pure const nothrow @safe @nogc {
37 int width () { pragma(inline, true); return (rdatap ? rdata.rwdt : 0); }
38 int height () { pragma(inline, true); return (rdatap ? rdata.rhgt : 0); }
39 bool solid () { pragma(inline, true); return (rdatap ? rdata.simple && rdata.rwdt > 0 && rdata.rhgt > 0 && rdata.simpleSolid : false); }
40 bool empty () { pragma(inline, true); return (rdatap ? rdata.rwdt < 1 || rdata.rhgt < 1 || (rdata.simple && !rdata.simpleSolid) : true); }
43 @property uint[] getData () const nothrow @safe {
44 uint[] res;
45 if (rdata.simple) {
46 res ~= 1|(rdata.simpleSolid ? 2 : 0);
47 } else {
48 res ~= 0;
50 res[0] |= cast(int)(SpanType.sizeof<<4);
51 res ~= rdata.rwdt;
52 res ~= rdata.rhgt;
53 if (!rdata.simple) {
54 res ~= rdata.lineofs;
55 foreach (SpanType d; rdata.data) res ~= d;
57 return res;
60 void setData (const(int)[] data) nothrow @safe {
61 cow!false();
62 if (data.length < 3 || (data[0]>>4) != SpanType.sizeof) assert(0, "invalid region data");
63 rdata.rwdt = data[1];
64 rdata.rhgt = data[2];
65 rdata.simple = ((data[0]&0x01) != 0);
66 rdata.lineofs = null;
67 rdata.data = null;
68 if (rdata.simple) {
69 rdata.simpleSolid = ((data[0]&0x02) != 0);
70 } else {
71 foreach (int d; data[3..3+rdata.rhgt]) rdata.lineofs ~= d;
72 foreach (int d; data[3+rdata.rhgt..$]) rdata.data ~= cast(SpanType)d;
76 // this creates solid region
77 this (int awidth, int aheight, bool solid=true) nothrow @safe @nogc { setSize(awidth, aheight, solid); }
78 ~this () nothrow @safe @nogc { decRC(); } // release this region data
79 this (this) nothrow @safe @nogc { if (rdatap) ++rdata.rc; } // share this region data
81 void setSize (int awidth, int aheight, bool solid=true) nothrow @safe @nogc {
82 if (awidth <= 0 || aheight <= 0) awidth = aheight = 0;
83 if (awidth > SpanType.max-1 || aheight > SpanType.max-1) assert(0, "Region internal error: region dimensions are too big");
84 cow!false();
85 rdata.rwdt = awidth;
86 rdata.rhgt = aheight;
87 rdata.simpleSolid = solid;
88 rdata.lineofs = null;
89 rdata.data = null;
92 /// is given point visible?
93 bool visible (int x, int y) const pure nothrow @safe @nogc {
94 // easiest cases
95 if (!rdatap) return false;
96 if (rdata.rwdt < 1 || rdata.rhgt < 1) return false;
97 if (x < 0 || y < 0 || x >= rdata.rwdt || y >= rdata.rhgt) return false;
98 if (rdata.simple) return true; // ok, easy case here
99 // now the hard one
100 immutable ldofs = rdata.lineofs[y];
101 immutable len = rdata.data[ldofs];
102 debug assert(len > 1);
103 auto line = rdata.data[ldofs+1..ldofs+len];
104 int idx = void; // will be initied in mixin
105 mixin(FindSpanMixinStr!"idx");
106 debug assert(idx < line.length); // too far (the thing that should not be)
107 return ((idx+(line[idx] == x))%2 == 0);
110 /// punch a hole
111 void punch (int x, int y, int w=1, int h=1) nothrow @trusted { pragma(inline, true); doPunchPatch!"punch"(x, y, w, h); }
113 /// patch a hole
114 void patch (int x, int y, int w=1, int h=1) nothrow @trusted { pragma(inline, true); doPunchPatch!"patch"(x, y, w, h); }
116 // ////////////////////////////////////////////////////////////////////////// //
117 enum State { Mixed = -1, Empty, Solid } //WARNING! don't change the order!
119 /// return span state %-)
120 State spanState (int y, int x0, int x1) const pure nothrow @safe @nogc {
121 if (rdata is null) return State.Empty;
122 if (y < 0 || y >= rdata.rhgt || x1 < 0 || x0 >= rdata.rwdt || x1 < x0) return State.Empty;
123 if (rdata.simple) {
124 // if our span is not fully inside, it can be either Empty or Mixed
125 if (rdata.simpleSolid) {
126 return (x0 >= 0 && x1 < rdata.rwdt ? State.Solid : State.Mixed);
127 } else {
128 return State.Empty;
131 immutable ldofs = rdata.lineofs[y];
132 immutable len = rdata.data[ldofs];
133 debug assert(len > 1);
134 auto line = rdata.data[ldofs+1..ldofs+len];
135 int idx = void; // will be initied in mixin
136 immutable x = (x0 >= 0 ? x0 : 0);
137 mixin(FindSpanMixinStr!"idx");
138 debug assert(idx < line.length); // too far (the thing that should not be)
139 // move to "real" span
140 if (line[idx] == x) ++idx;
141 // now, sx is line[idx-1], ex is line[idx]
142 if (x1 >= (idx < line.length ? line[idx] : rdata.rwdt)) return State.Mixed;
143 idx = (idx^1)&1; // we are interested only in last bit, and we converted it to State here
144 // if our span is not fully inside, it can be either Empty or Mixed
145 if (idx == State.Solid && x0 < 0) return State.Mixed;
146 return cast(State)idx;
149 static private template IsGoodSDG(T) {
150 private import std.traits;
151 static private template IsGoodRT(T) { enum IsGoodRT = is(T == void) || is(T == bool) || is(T : int); }
152 static private template IsGoodAT(T) { enum IsGoodAT = is(T == int) || is(T == long) || is(T == uint) || is(T == ulong); }
153 enum IsGoodSDG = isCallable!T && IsGoodRT!(ReturnType!T) && (variadicFunctionStyle!T == Variadic.no) &&
154 Parameters!T.length == 2 && IsGoodAT!(Parameters!T[0]) && IsGoodAT!(Parameters!T[1]);
157 /// call delegate for each solid or empty span
158 /// for non-void returning delegates, return !0 to exit
159 auto spans(bool solids=true, DG) (int y, int x0, int x1, scope DG dg) const if (IsGoodSDG!DG) { return spansEnumerator!(DG, solids)(y, 0, x0, x1, dg); }
161 /// call delegate for each solid or empty span
162 /// for non-void returning delegates, return !0 to exit
163 /// `ofsx` will be automatically subtracted from `x0` and `x1` args, and added to `x0` and `x1` delegate args
164 auto spans(bool solids=true, DG) (int y, int ofsx, int x0, int x1, scope DG dg) const if (IsGoodSDG!DG) { return spansEnumerator!(DG, solids)(y, ofsx, x0, x1, dg); }
166 /// element of span range
167 static struct XPair { int x0, x1; }
169 /// get range of spans
170 auto spanRange(bool solids=true) (int y, int x0, int x1) nothrow @safe @nogc { return spanRange!solids(y, 0, x0, x1); }
171 /// ditto
172 auto spanRange(bool solids=true) (int y, int ofsx, int x0, int x1) nothrow @safe @nogc {
173 static struct SpanRange(bool solids) {
174 int ofsx, x0, x1, rwdt, idx;
175 ubyte eosNM; // empty(bit0), nomore(bit1) ;-)
176 XPair fpair; // front
177 const(SpanType)[] line;
179 nothrow @trusted @nogc:
180 this (ref Region reg, int y, int aofsx, int ax0, int ax1) {
181 ofsx = aofsx;
182 x0 = ax0;
183 x1 = ax1;
184 if (x0 > x1) { eosNM = 0x01; return; }
185 if (reg.rdata is null) {
186 static if (!solids) {
187 fpair.x0 = x0;
188 fpair.x1 = x1;
189 eosNM = 0x02;
190 } else {
191 eosNM = 0x01;
193 return;
195 rwdt = reg.rdata.rwdt;
196 x0 -= ofsx;
197 x1 -= ofsx;
198 if (y < 0 || y >= reg.rdata.rhgt || x1 < 0 || x0 >= rwdt || x1 < x0) {
199 static if (!solids) {
200 fpair.x0 = x0+ofsx;
201 fpair.x1 = x1+ofsx;
202 eosNM = 0x02;
203 } else {
204 eosNM = 0x01;
206 return;
208 if (reg.rdata.simple) {
209 if (reg.rdata.simpleSolid) {
210 static if (solids) {
211 if (x0 < 0) x0 = 0;
212 if (x1 >= rwdt) x1 = rwdt-1;
213 if (x0 <= x1) {
214 fpair.x0 = x0+ofsx;
215 fpair.x1 = x1+ofsx;
216 eosNM = 0x02;
217 } else {
218 eosNM = 0x01;
220 } else {
221 if (x0 < 0) {
222 fpair.x0 = x0+ofsx;
223 fpair.x1 = -1+ofsx;
224 eosNM = (x1 < rwdt ? 0x02 : 0x04);
225 } else {
226 if (x1 >= rwdt) {
227 fpair.x0 = rwdt+ofsx;
228 fpair.x1 = x1+ofsx;
229 eosNM = 0x02;
230 } else {
231 eosNM = 0x01;
235 } else {
236 static if (!solids) {
237 fpair.x0 = x0+ofsx;
238 fpair.x1 = x1+ofsx;
239 eosNM = 0x02;
240 } else {
241 eosNM = 0x01;
244 return;
246 // edge cases are checked
247 immutable ldofs = reg.rdata.lineofs[y];
248 immutable len = reg.rdata.data[ldofs];
249 debug assert(len > 1);
250 line = reg.rdata.data[ldofs+1..ldofs+len];
251 // beyond left border? move to first solid span
252 bool hasOne = false;
253 if (x0 < 0) {
254 int ex = (line[0] == 0 ? line[1]-1 : -1);
255 // is first span empty too?
256 if (ex >= x1) {
257 static if (!solids) {
258 fpair.x0 = x0+ofsx;
259 fpair.x1 = x1+ofsx;
260 eosNM = 0x02;
261 } else {
262 eosNM = 0x01;
264 return;
266 static if (!solids) {
267 fpair.x0 = x0+ofsx;
268 fpair.x1 = ex+ofsx;
269 hasOne = true;
270 if (x0 == -9) {
271 //import iv.writer; writeln("*");
274 x0 = ex+1;
276 static if (solids) { if (x1 >= rwdt) x1 = rwdt-1; }
277 //int idx = void; // will be initied in mixin
278 alias x = x0; // for mixin
279 mixin(FindSpanMixinStr!"idx");
280 debug assert(idx < line.length); // too far (the thing that should not be)
281 // move to "real" span, so sx is line[idx-1], ex+1 is line[idx]
282 if (line[idx] == x) ++idx;
283 if (!hasOne) popFront();
286 @property auto save () pure {
287 SpanRange!solids res;
288 res.ofsx = ofsx;
289 res.x0 = x0;
290 res.x1 = x1;
291 res.rwdt = rwdt;
292 res.idx = idx;
293 res.eosNM = eosNM;
294 res.fpair = fpair;
295 res.line = line;
296 return res;
299 @property bool empty () const pure { return ((eosNM&0x01) != 0); }
300 @property XPair front () const pure { return XPair(fpair.x0, fpair.x1); }
302 void popFront () {
303 if (eosNM&0x02) eosNM = 0x01;
304 if (eosNM&0x01) return;
305 // edge case
306 if (eosNM&0x04) {
307 if (x1 >= rwdt) {
308 static if (!solids) {
309 fpair.x0 = rwdt+ofsx;
310 fpair.x1 = x1+ofsx;
311 eosNM = 0x02;
312 } else {
313 eosNM = 0x01;
315 } else {
316 eosNM = 0x01;
318 return;
320 bool hasOne = false;
321 // process spans
322 while (x0 <= x1) {
323 int ex = line[idx]-1;
324 int cex = (ex < x1 ? ex : x1); // clipped ex
325 // emit part from x0 to ex if necessary
326 static if (solids) {
327 // current span is solid?
328 if (idx%2 == 0) {
329 fpair.x0 = x0+ofsx;
330 fpair.x1 = cex+ofsx;
331 hasOne = true;
333 } else {
334 // current span is empty?
335 if (idx%2 == 1) {
336 fpair.x0 = x0+ofsx;
337 fpair.x1 = (ex < rwdt-1 ? cex : x1)+ofsx;
338 hasOne = true;
339 if (ex == rwdt-1) { eosNM = 0x02; return; }
340 } else {
341 if (ex == rwdt-1) { x0 = rwdt; break; }
344 x0 = ex+1;
345 ++idx;
346 if (hasOne) return;
348 if (hasOne) return;
349 static if (!solids) {
350 if (x0 <= x1) {
351 fpair.x0 = x0+ofsx;
352 fpair.x1 = x1+ofsx;
353 eosNM = 0x02;
354 return;
357 eosNM = 0x01;
361 return SpanRange!solids(this, y, ofsx, x0, x1);
364 private:
365 // ////////////////////////////////////////////////////////////////////////// //
366 auto spansEnumerator(DG, bool solids) (int y, int ofsx, int x0, int x1, scope DG dg) const {
367 import std.traits : ReturnType;
368 static if (is(ReturnType!DG == void)) {
369 enum ReturnFail = "return;";
370 enum DgCall(string args) = "dg("~args~");";
371 } else {
372 static if (is(ReturnType!DG == bool)) enum ReturnFail = "return false;"; else enum ReturnFail = "return 0;";
373 enum DgCall(string args) = "if (auto xres = dg("~args~")) return xres;";
375 if (x0 > x1 || dg is null) mixin(ReturnFail);
376 if (rdata is null) {
377 static if (!solids) dg(x0, x1);
378 mixin(ReturnFail);
380 x0 -= ofsx;
381 x1 -= ofsx;
382 if (y < 0 || y >= rdata.rhgt || x1 < 0 || x0 >= rdata.rwdt || x1 < x0) {
383 static if (!solids) { mixin(DgCall!"x0+ofsx, x1+ofsx"); }
384 mixin(ReturnFail);
386 if (rdata.simple) {
387 if (rdata.simpleSolid) {
388 static if (solids) {
389 if (x0 < 0) x0 = 0;
390 if (x1 >= rdata.rwdt) x1 = rdata.rwdt-1;
391 if (x0 <= x1) { mixin(DgCall!"x0+ofsx, x1+ofsx"); }
392 } else {
393 if (x0 < 0) { mixin(DgCall!"x0+ofsx, -1+ofsx"); }
394 if (x1 >= rdata.rwdt) { mixin(DgCall!"rdata.rwdt+ofsx, x1+ofsx"); }
396 } else {
397 static if (!solids) { mixin(DgCall!"x0+ofsx, x1+ofsx"); }
399 mixin(ReturnFail);
401 immutable ldofs = rdata.lineofs[y];
402 immutable len = rdata.data[ldofs];
403 debug assert(len > 1);
404 auto line = rdata.data[ldofs+1..ldofs+len];
405 // beyond left border? move to first solid span
406 if (x0 < 0) {
407 int ex = (rdata.data[ldofs+1] == 0 ? rdata.data[ldofs+2]-1 : -1);
408 // is first span empty too?
409 if (ex >= x1) { static if (!solids) { mixin(DgCall!"x0+ofsx, x1+ofsx"); } mixin(ReturnFail); }
410 static if (!solids) { mixin(DgCall!"x0+ofsx, ex+ofsx"); }
411 x0 = ex+1;
413 static if (solids) { if (x1 >= rdata.rwdt) x1 = rdata.rwdt-1; }
414 int idx = void; // will be initied in mixin
415 alias x = x0; // for mixin
416 mixin(FindSpanMixinStr!"idx");
417 debug assert(idx < line.length); // too far (the thing that should not be)
418 // move to "real" span, so sx is line[idx-1], ex+1 is line[idx]
419 if (line[idx] == x) ++idx;
420 // process spans
421 while (x0 <= x1) {
422 int ex = line[idx]-1;
423 int cex = (ex < x1 ? ex : x1); // clipped ex
424 // emit part from x0 to ex if necessary
425 static if (solids) {
426 // current span is solid?
427 if (idx%2 == 0) { mixin(DgCall!"x0+ofsx, cex+ofsx"); }
428 } else {
429 // current span is empty?
430 if (idx%2 == 1) {
431 { mixin(DgCall!"x0+ofsx, (ex < rdata.rwdt-1 ? cex : x1)+ofsx"); }
432 if (ex == rdata.rwdt-1) mixin(ReturnFail);
433 } else {
434 if (ex == rdata.rwdt-1) { x0 = rdata.rwdt; break; }
437 x0 = ex+1;
438 ++idx;
439 //static if (!solids) { if (x0 == rdata.rwdt) break; }
441 static if (!solids) { if (x0 <= x1) { mixin(DgCall!"x0+ofsx, x1+ofsx"); } }
442 mixin(ReturnFail);
445 // ////////////////////////////////////////////////////////////////////////// //
446 // find element index
447 // always returns index of key which is >= `x`
448 private enum FindSpanMixinStr(string minAndRes) = "{
449 ("~minAndRes~") = 0;
450 int max = cast(int)line.length;
451 while (("~minAndRes~") < max) {
452 int mid = (("~minAndRes~")+max)>>1; // ignore possible overflow, it can't happen here
453 debug assert(mid < max);
454 if (line[mid] < x) ("~minAndRes~") = mid+1; else max = mid;
456 //return ("~minAndRes~"); // actually, the key is found if (max == min/*always*/ && min < line.length && line[min] == x)
459 // ////////////////////////////////////////////////////////////////////////// //
460 // punch a hole, patch a hole
461 // mode: "punch", "patch"
462 //FIXME: overflows
463 void doPunchPatch(string mode) (int x, int y, int w=1, int h=1) nothrow @trusted {
464 static assert(mode == "punch" || mode == "patch", "Region: invalid mode: "~mode);
465 if (rdata is null) return;
466 static if (mode == "punch") {
467 if (empty) return;
468 } else {
469 if (solid) return;
471 if (w < 1 || h < 1) return;
472 if (x >= rdata.rwdt || y >= rdata.rhgt) return;
473 //TODO: overflow check
474 if (x < 0) {
475 if (x+w <= 0) return;
476 w += x;
477 x = 0;
479 if (y < 0) {
480 if (y+h <= 0) return;
481 h += y;
482 y = 0;
484 int x1 = x+w-1;
485 if (x1 >= rdata.rwdt) x1 = rdata.rwdt-1;
486 debug assert(x <= x1);
487 int y1 = y+h-1;
488 if (y1 >= rdata.rhgt) y1 = rdata.rhgt-1;
489 debug assert(y <= y1);
490 foreach (int cy; y..y1+1) doPunchPatchLine!mode(cy, x, x1);
494 // ////////////////////////////////////////////////////////////////////////// //
495 void makeRoom (usize ofs, ssize count) nothrow @trusted {
496 import core.stdc.string : memmove;
497 debug assert(ofs <= rdata.data.length);
498 if (count > 0) {
499 // make room
500 // `assumeSafeAppend` was already called in caller
501 //rdata.data.assumeSafeAppend.length += count;
502 rdata.data.length += count;
503 if (ofs+count < rdata.data.length) memmove(rdata.data.ptr+ofs+count, rdata.data.ptr+ofs, SpanType.sizeof*(rdata.data.length-ofs-count));
504 } else if (count < 0) {
505 // remove rdata.data
506 count = -count;
507 debug assert(ofs+count <= rdata.data.length);
508 if (ofs+count == rdata.data.length) {
509 rdata.data.length = ofs;
510 } else {
511 immutable auto left = rdata.data.length-ofs-count;
512 memmove(rdata.data.ptr+ofs, rdata.data.ptr+ofs+count, SpanType.sizeof*(rdata.data.length-ofs-count));
513 rdata.data.length -= count;
515 //rdata.data.assumeSafeAppend; // in case we will want to grow later
519 // replace span data at plofs with another data from spofs, return # of bytes added (or removed, if negative)
520 ssize replaceSpanData (usize plofs, usize spofs) nothrow @trusted {
521 //import core.stdc.string : memcpy;
522 debug assert(spofs < rdata.data.length && spofs+rdata.data[spofs] == rdata.data.length);
523 debug assert(plofs <= spofs && plofs+rdata.data[plofs] <= spofs);
524 if (plofs == spofs) return 0; // nothing to do; just in case
525 auto oldlen = rdata.data[plofs];
526 auto newlen = rdata.data[spofs];
527 // same length?
528 ssize ins = cast(ssize)newlen-cast(ssize)oldlen;
529 if (ins) {
530 makeRoom(plofs, ins);
531 spofs += ins;
533 if (newlen > 0) rdata.data[plofs..plofs+newlen] = rdata.data[spofs..spofs+newlen]; //memcpy(rdata.data.ptr+plofs, rdata.data.ptr+spofs, SpanType.sizeof*newlen);
534 return ins;
537 // insert span data from spofs at plofs
538 void insertSpanData (usize plofs, usize spofs) nothrow @trusted {
539 //import core.stdc.string : memcpy;
540 debug assert(spofs < rdata.data.length && spofs+rdata.data[spofs] == rdata.data.length);
541 debug assert(plofs <= spofs);
542 if (plofs == spofs) return; // nothing to do; just in case
543 auto newlen = rdata.data[spofs];
544 makeRoom(plofs, newlen);
545 spofs += newlen;
546 rdata.data[plofs..plofs+newlen] = rdata.data[spofs..spofs+newlen];
547 //memcpy(rdata.data.ptr+plofs, rdata.data.ptr+spofs, SpanType.sizeof*newlen);
550 bool isEqualLines (int y0, int y1) nothrow @trusted @nogc {
551 import core.stdc.string : memcmp;
552 if (y0 < 0 || y1 < 0 || y0 >= rdata.rhgt || y1 >= rdata.rhgt) return false;
553 auto ofs0 = rdata.lineofs[y0];
554 auto ofs1 = rdata.lineofs[y1];
555 if (rdata.data[ofs0] != rdata.data[ofs1]) return false;
556 return (memcmp(rdata.data.ptr+ofs0, rdata.data.ptr+ofs1, SpanType.sizeof*rdata.data[ofs0]) == 0);
559 // all args must be valid
560 // [x0..x1]
561 void doPunchPatchLine(string mode) (int y, int x0, int x1) nothrow @trusted {
562 static if (mode == "patch") {
563 if (rdata.simple && rdata.simpleSolid) return; // no need to patch completely solid region
564 } else {
565 if (rdata.simple && !rdata.simpleSolid) return; // no need to patch completely empty region
568 // check if we really have to do anything here
569 static if (mode == "patch") {
570 if (spanState(y, x0, x1) == State.Solid) return;
571 //enum psmode = true;
572 enum op = CombineOp.Or;
573 } else {
574 if (spanState(y, x0, x1) == State.Empty) return;
575 //enum psmode = false;
576 enum op = CombineOp.Cut;
579 doCombine(y, (uint lofs, ref SpanType[] dptr) nothrow @trusted {
580 // note that after `assumeSafeAppend` we can increase length without further `assumeSafeAppend` calls
581 debug(region_more_prints) { import core.stdc.stdio : printf; printf("op=%d; x0=%d; x1=%d; rwdt=%d\n", cast(int)op, x0, x1, rdata.rwdt); }
582 SpanType dsp = cast(SpanType)(x1-x0+1);
583 debug(region_more_prints) {
584 import core.stdc.stdio : printf;
585 auto cspd = CSPD(op, &dsp, x1-x0+1, x0);
586 while (!cspd.empty) {
587 printf(" (%d,%d,%d)", cspd.sx, cspd.ex, (cspd.solid ? 1 : 0));
588 cspd.popFront();
590 printf("\n");
592 combineSpans(
593 (int x) nothrow @trusted { dptr ~= cast(SpanType)x; },
594 CSPD(CombineOp.Or, rdata.data.ptr+lofs+1, rdata.rwdt), // base span
595 CSPD(op, &dsp, x1-x0+1, x0),
601 static void combineSpans(SPR...) (scope void delegate (int x) nothrow @safe putX, auto ref SPR spans) if (SPR.length > 1) {
602 // all args must be valid
603 void doPunchPatchLine(SPR...) (CombineOp op, int y, auto ref SPR spans) nothrow @trusted if (SPR.length > 0) {
604 if (rdata.simple) {
605 if (op == CombineOp.Or && rdata.simpleSolid) return; // no need to patch completely solid region
606 if ((op == CombineOp.And || op == CombineOp.Cut || op == op == CombineOp.NCut) && !rdata.simpleSolid) return; // no need to patch completely empty region
611 // `combine`: `lofs` is starting index in `rdata.data` for base line (i.e. length element)
612 // it should build a new valid line data, starting from `rdata.data.length`, not including line length tho
613 // all args must be valid
614 void doCombine() (int y, scope void delegate (uint lofs, ref SpanType[] dptr) nothrow @trusted combine) nothrow @trusted {
615 // bad luck, build new line
616 cow!true();
617 if (rdata.simple) {
618 // build complex region rdata.data
619 if (rdata.lineofs is null) {
620 rdata.lineofs.length = rdata.rhgt; // allocate and clear
621 } else {
622 if (rdata.lineofs.length < rdata.rhgt) rdata.lineofs.assumeSafeAppend;
623 rdata.lineofs.length = rdata.rhgt;
624 rdata.lineofs[] = 0; // clear
626 rdata.data.length = 0;
627 if (rdata.simpleSolid) {
628 rdata.data.assumeSafeAppend ~= 2; // length
629 } else {
630 rdata.data.assumeSafeAppend ~= 3; // length
631 rdata.data ~= 0; // dummy solid
633 rdata.data ~= cast(SpanType)rdata.rwdt; // the only span
634 rdata.simple = false;
637 auto lofs = rdata.lineofs[y]; // current line offset
638 int lsize = rdata.data.ptr[lofs]; // current line size
639 auto tmppos = cast(uint)rdata.data.length; // starting position of the new line data
641 //patchSpan!psmode(lofs+1, x0, x1);
642 rdata.data.assumeSafeAppend ~= 0; // length
643 combine(lofs, rdata.data);
644 debug(region_more_prints) { import core.stdc.stdio : printf; printf("LEN=%d\n", cast(int)(rdata.data.length-tmppos)); }
645 if (rdata.data.length-tmppos > SpanType.max) assert(0, "region internal error: line data too big");
646 rdata.data.ptr[tmppos] = cast(SpanType)(rdata.data.length-tmppos);
648 debug(region_more_prints) {
649 import core.stdc.stdio : printf;
650 foreach (SpanType t; rdata.data[tmppos..$]) printf(" %u", cast(uint)t);
651 printf("\n");
654 int newsize = rdata.data[tmppos]; // size of the new line
656 // was this line first in slab?
657 auto prevofs = (y > 0 ? rdata.lineofs[y-1] : -1);
658 auto nextofs = (y+1 < rdata.rhgt ? rdata.lineofs[y+1] : -2);
660 // place new line data, breaking span if necessary
661 if (prevofs != lofs && nextofs != lofs) {
662 // we were a slab on our own?
663 // replace line
664 auto delta = replaceSpanData(lofs, tmppos);
665 tmppos += delta;
666 if (delta) foreach (ref ofs; rdata.lineofs[y+1..$]) ofs += delta;
667 } else if (prevofs != lofs && nextofs == lofs) {
668 // we were a slab start
669 // insert at lofs
670 insertSpanData(lofs, tmppos);
671 tmppos += newsize;
672 foreach (ref ofs; rdata.lineofs[y+1..$]) ofs += newsize;
673 } else if (prevofs == lofs && nextofs != lofs) {
674 // we were a slab end
675 // insert after lofs
676 lofs += lsize;
677 insertSpanData(lofs, tmppos);
678 tmppos += newsize;
679 rdata.lineofs[y] = lofs;
680 foreach (ref ofs; rdata.lineofs[y+1..$]) ofs += newsize;
681 } else {
682 //import core.stdc.string : memcpy;
683 // we were a slab brick
684 debug assert(prevofs == lofs && nextofs == lofs);
685 // the most complex case
686 // insert us after lofs, insert slab start after us, fix slab and offsets
687 // insert us
688 lofs += lsize;
689 insertSpanData(lofs, tmppos);
690 tmppos += newsize;
691 rdata.lineofs[y] = lofs;
692 // insert old slab start
693 lofs += newsize;
694 lsize = rdata.data[prevofs];
695 makeRoom(lofs, lsize);
696 //memcpy(rdata.data.ptr+lofs, rdata.data.ptr+prevofs, SpanType.sizeof*lsize);
697 rdata.data[lofs..lofs+lsize] = rdata.data[prevofs..prevofs+lsize];
698 // fix current slab
699 int ny = y+1;
700 while (ny < rdata.rhgt && rdata.lineofs[ny] == prevofs) rdata.lineofs[ny++] = lofs;
701 // fix offsets
702 newsize += lsize; // simple optimization
703 while (ny < rdata.rhgt) rdata.lineofs[ny++] += newsize;
704 newsize -= lsize;
707 // remove extra data
708 lofs = rdata.lineofs[$-1];
709 rdata.data.length = lofs+rdata.data[lofs];
711 // now check if we can join slabs
712 // of course, this is somewhat wasteful, but if we'll combine this
713 // check with previous code, the whole thing will explode to an
714 // unmaintainable mess; anyway, we aren't on ZX Spectrum
716 bool upequ = isEqualLines(y-1, y);
717 bool dnequ = isEqualLines(y+1, y);
718 if (upequ || dnequ) {
719 rdata.data.assumeSafeAppend; // we have to call it after shrinking
720 lofs = rdata.lineofs[y];
721 debug assert(rdata.data[lofs] == newsize);
722 makeRoom(lofs, -newsize); // drop us
723 if (upequ && dnequ) {
724 // join prev and next slabs by removing two lines...
725 auto pofs = rdata.lineofs[y-1];
726 makeRoom(lofs, -newsize); // drop next line
727 newsize *= 2;
728 // and fixing offsets
729 rdata.lineofs[y++] = pofs;
730 auto sofs = rdata.lineofs[y];
731 while (y < rdata.rhgt && rdata.lineofs[y] == sofs) rdata.lineofs[y++] = pofs;
732 } else if (upequ) {
733 // join prev slab
734 rdata.lineofs[y] = rdata.lineofs[y-1];
735 ++y;
736 } else if (dnequ) {
737 // lead next slab
738 auto sofs = rdata.lineofs[++y];
739 while (y < rdata.rhgt && rdata.lineofs[y] == sofs) rdata.lineofs[y++] = lofs;
741 // fix offsets
742 foreach (ref ofs; rdata.lineofs[y..$]) ofs -= newsize;
746 // check if we can collapse this region
747 if (rdata.data.length == 2) {
748 if (rdata.data.ptr[0] != 2 || rdata.data.ptr[1] != rdata.rwdt) return;
749 } else if (rdata.data.length == 3) {
750 if (rdata.data.ptr[0] != 3 || rdata.data.ptr[1] != 0 || rdata.data.ptr[2] != rdata.rwdt) return;
751 } else {
752 return;
754 foreach (immutable ofs; rdata.lineofs[1..$]) if (ofs != 0) return;
756 rdata.simple = true;
757 //static if (mode == "patch") rdata.simpleSolid = true; else rdata.simpleSolid = false;
758 rdata.simpleSolid = (rdata.data.length == 2);
759 rdata.lineofs.length = 0; // we may need it later, so keep it
762 static struct CSPD {
763 nothrow @trusted @nogc:
764 const(SpanType)* data;
765 int width; // to detect span end
766 int xofs;
767 CombineOp op; // operation
768 bool dsolid; // current span
769 int csx;
771 this (CombineOp aop, const(SpanType)* adata, int awdt, int axofs=0) {
772 // if first span is zero-sized, this region starts with empty span
773 op = aop;
774 width = awdt;
775 xofs = axofs;
776 if (*adata == 0) {
777 dsolid = false;
778 ++adata;
779 } else {
780 dsolid = true;
782 data = adata;
785 this() (CombineOp aop, auto ref Region rg, int axofs=0) {
786 this(aop, rg.ldata.ptr, rg.width, axofs);
789 @disable this (this); // no copies
791 @property bool empty () const pure { pragma(inline, true); return (data is null); }
792 @property bool solid () const pure { pragma(inline, true); return dsolid; }
793 @property int sx () const pure { pragma(inline, true); return xofs+csx; }
794 @property int ex () const pure { pragma(inline, true); return xofs+(*data)-1; }
795 void popFront () {
796 pragma(inline, true);
797 csx = *data++;
798 if (csx >= width) data = null;
799 dsolid = !dsolid;
803 // spans[0] should have `int .width`, `empty`, `popFront`, `sx`, `ex`, `solid`
804 // others sould have: `empty`, `popFront`, `sx`, `ex`, `solid`, `op`
805 // spans[0] should always start at 0 (i.e. it is alpha and omega)
806 static void combineSpans(SPR...) (scope void delegate (int x) nothrow @safe putX, auto ref SPR spans) if (SPR.length > 1) {
807 bool lastsolid = true; // it's ok
808 int lastsx = 0; // it's ok
810 void pushSpan() (int ex, bool solid) {
811 debug(region_more_prints) {} else pragma(inline, true);
812 //debug(region_more_prints) { import core.stdc.stdio : printf; printf(" ex=%d; solid=%d; lastsx=%d; lastsolid=%d\n", ex, (solid ? 1 : 0), lastsx, (lastsolid ? 1 : 0)); }
813 //debug if (ex <= lastsx) { import core.stdc.stdio : printf; printf("ex=%d; lastsx=%d\n", ex, lastsx); }
814 debug assert(ex >= lastsx);
815 if (solid != lastsolid) {
816 lastsolid = solid;
817 putX(lastsx); // new span starts here
818 debug(region_more_prints) { import core.stdc.stdio : printf; printf(" EMIT: %d\n", lastsx); }
820 lastsx = ex+1;
823 debug assert(!spans[0].empty);
824 debug assert(spans[0].sx == 0);
825 immutable sp0w = spans[0].width;
826 int cursx = 0;
827 while (!spans[0].empty) {
828 // process other spans
829 bool seenAliveSpan = false;
830 bool nsolid = spans[0].solid;
831 int nex = spans[0].ex;
832 foreach (ref sp; spans[1..$]) {
833 while (!sp.empty && sp.ex < cursx) sp.popFront();
834 if (sp.empty) continue;
835 seenAliveSpan = true;
836 debug(region_more_prints) { import core.stdc.stdio : printf; printf(" cursx=%d; nex=%d; nsolid=%d; sp.sx=%d; sp.ex=%d; sp.solid=%d\n", cursx, nex, (nsolid ? 1 : 0), sp.sx, sp.ex, (sp.solid ? 1 : 0)); }
837 //debug if (sp.sx > cursx) { import core.stdc.stdio : printf; printf("cursx=%d; sp.sx=%d; sp.ex=%d; sp.solid=%d\n", cursx, sp.sx, sp.ex, (sp.solid ? 1 : 0)); }
838 //debug assert(sp.sx <= cursx);
839 if (sp.sx > nex) continue; // too far
840 if (sp.sx > cursx) { nex = sp.sx-1; continue; } // partial
841 // do logic op
842 final switch (sp.op) {
843 case CombineOp.Or: nsolid = nsolid || sp.solid; break;
844 case CombineOp.And: nsolid = nsolid && sp.solid; break;
845 case CombineOp.Xor: if (sp.solid) nsolid = !nsolid; break;
846 case CombineOp.Cut: if (sp.solid) nsolid = false; break;
847 case CombineOp.NCut: if (!sp.solid) nsolid = false; break;
849 if (sp.ex < nex) nex = sp.ex;
851 pushSpan(nex, nsolid);
852 if (!seenAliveSpan) {
853 // no more alive spans, process span0 till the end
854 debug(region_more_prints) { import core.stdc.stdio : printf; printf(" NM!\n"); }
855 if (nex < spans[0].ex) pushSpan(spans[0].ex, spans[0].solid); // finish current span
856 for (;;) {
857 spans[0].popFront();
858 if (spans[0].empty) break;
859 pushSpan(spans[0].ex, spans[0].solid);
861 // put sentinel
862 debug assert(lastsx <= sp0w);
863 putX(sp0w);
864 return;
866 if (nex < spans[0].ex) {
867 // something was done, and first slab of span0 is not completely eaten
868 cursx = nex+1;
869 } else {
870 // either no alive spans, or first slab of span0 is completely eaten
871 spans[0].popFront();
872 if (spans[0].empty) { putX(sp0w); return; } // done
873 cursx = spans[0].sx;
876 // put sentinel
877 debug assert(lastsx <= sp0w);
878 putX(sp0w);
881 private:
882 usize rdatap = 0; // hide from GC
884 @property inout(RData)* rdata () inout pure const nothrow @trusted @nogc { static if (__VERSION__ > 2067) pragma(inline, true); return cast(RData*)rdatap; }
886 void decRC () nothrow @trusted @nogc {
887 if (rdatap != 0) {
888 if (--rdata.rc == 0) {
889 import core.memory : GC;
890 import core.stdc.stdlib : free;
891 GC.removeRange(rdata);
892 free(rdata);
894 rdatap = 0; // just in case
898 // copy-on-write mechanics
899 void cow(bool doCopyData) () nothrow @trusted {
900 auto srcd = rdata;
901 if (srcd is null || srcd.rc != 1) {
902 import core.memory : GC;
903 import core.stdc.stdlib : malloc, free;
904 import core.stdc.string : memcpy;
905 auto dstd = cast(RData*)malloc(RData.sizeof);
906 if (dstd is null) assert(0, "Region: out of memory"); // this is unlikely, and hey, just crash
907 // init with default values
908 //*dstd = RData.init;
909 static immutable RData initr = RData.init;
910 memcpy(dstd, &initr, RData.sizeof);
911 //(*dstd).__ctor();
912 if (srcd !is null) {
913 // copy
914 dstd.rwdt = srcd.rwdt;
915 dstd.rhgt = srcd.rhgt;
916 dstd.simple = srcd.simple;
917 dstd.simpleSolid = srcd.simpleSolid;
918 dstd.lineofs = null;
919 dstd.data = null;
920 dstd.rc = 1;
921 static if (doCopyData) {
922 if (!dstd.simple) {
923 // copy complex region
924 if (srcd.lineofs.length) dstd.lineofs = srcd.lineofs.dup;
925 if (srcd.data.length) dstd.data = srcd.data.dup;
928 --srcd.rc;
929 assert(srcd.rc > 0);
931 rdatap = cast(usize)dstd;
932 GC.addRange(rdata, RData.sizeof, typeid(RData));
936 // region data
937 // all data is here, so passing region struct around is painless
938 static struct RData {
939 int rwdt, rhgt; // width and height
940 bool simple = true; // is this region a simple one (i.e. rectangular, without holes)?
941 bool simpleSolid = true; // if it is simple, is it solid or empty?
942 //WARNING! the following arrays should NEVER be shared!
943 uint[] lineofs; // line data offset in data[]
944 SpanType[] data;
945 // data format for each line:
946 // len, data[len-1]
947 // line items: list of increasing x coords; each coord marks start of next region
948 // all even regions are solid, i.e.
949 // 0..line[0]-1: solid region
950 // line[0]..line[1]-1: transparent (empty) region
951 // etc.
952 // note that line[$-1] is always rwdt; it's used as sentinel too
953 // `line[0] == 0` means that first span is transparent (empty)
954 // (i.e. region starts from transparent span)
955 int rc = 1; // refcount
960 // ////////////////////////////////////////////////////////////////////////// //
961 version(sdpy_region_test) {
962 //static assert(0);
963 import iv.writer;
966 void dumpData (ref Region reg) {
967 import iv.writer;
968 if (reg.rdata.simple) { writeln("simple ", (reg.rdata.simpleSolid ? "solid" : "empty"), " region"); return; }
969 foreach (immutable y, uint ofs; reg.rdata.lineofs) {
970 if (y > 0 && reg.rdata.lineofs[y-1] == ofs) {
971 writefln!"%5s:%3s: ditto"(ofs, y);
972 } else {
973 writef!"%5s:%3s: len="(ofs, y);
974 write(reg.rdata.data[ofs]);
975 auto end = ofs+reg.rdata.data[ofs];
976 ++ofs;
977 while (ofs < end) write("; ", reg.rdata.data[ofs++]);
978 writeln;
984 void checkLineOffsets (ref Region reg) {
985 if (reg.rdata.simple) return;
986 foreach (immutable idx; 1..reg.rdata.lineofs.length) {
987 if (reg.rdata.lineofs[idx-1] > reg.rdata.lineofs[idx]) assert(0, "invalid line offset data");
988 // check for two equal, but unmerged lines
989 if (reg.rdata.lineofs[idx-1] != reg.rdata.lineofs[idx]) {
990 import core.stdc.string : memcmp;
991 if (reg.rdata.data[reg.rdata.lineofs[idx-1]] == reg.rdata.data[reg.rdata.lineofs[idx]] &&
992 memcmp(reg.rdata.data.ptr+reg.rdata.lineofs[idx-1], reg.rdata.data.ptr+reg.rdata.lineofs[idx], reg.SpanType.sizeof*reg.rdata.data[reg.rdata.lineofs[idx]]) == 0)
994 dumpData(reg);
995 assert(0, "found two identical, but not merged lines");
997 if (reg.rdata.data[reg.rdata.lineofs[idx-1]] < 2) assert(0, "bad data (0)");
998 if (reg.rdata.data[reg.rdata.lineofs[idx]] < 2) assert(0, "bad data (1)");
1004 void buildBitmap (ref Region reg, int[] bmp) {
1005 if (reg.rdata.simple) {
1006 bmp[0..reg.width*reg.height] = (reg.rdata.simpleSolid ? 1 : 0);
1007 return;
1009 bmp[0..reg.width*reg.height] = 42;
1010 foreach (immutable y, uint ofs; reg.rdata.lineofs) {
1011 usize a = y*reg.width;
1012 usize len = reg.rdata.data[ofs++];
1013 if (len < 1) assert(0, "invalid span");
1014 int sx = 0;
1015 bool solid = true;
1016 if (reg.rdata.data[ofs] == 0) { solid = false; ++ofs; }
1017 while (sx != reg.width) {
1018 // we should not have two consecutive zero-width spans
1019 if (reg.rdata.data[ofs] == 0 || reg.rdata.data[ofs] <= sx) {
1020 //foreach (immutable idx; 0..reg.rdata.data.length) if (reg.rdata.data[idx] >= 0) writeln(idx, ": ", reg.rdata.data[idx]); else break;
1021 //assert(reg.rdata.data[ofs+1] != 0);
1022 assert(0, "invalid span");
1024 int ex = reg.rdata.data[ofs++];
1025 bmp[a+sx..a+ex] = (solid ? 1 : 0);
1026 solid = !solid;
1027 sx = ex;
1029 debug assert(sx == reg.width);
1031 foreach (immutable v; bmp[0..reg.width*reg.height]) if (v == 42) assert(0, "invalid region data");
1035 int[] buildCoords (int[] bmp, int type, int x0, int x1) {
1036 bool isSolid (int x) { return (x >= 0 && x < bmp.length && bmp[x] != 0); }
1037 int[] res;
1038 while (x0 <= x1) {
1039 while (x0 <= x1 && type != isSolid(x0)) ++x0;
1040 if (x0 > x1) break;
1041 res ~= x0; // start
1042 while (x0 <= x1 && type == isSolid(x0)) ++x0;
1043 res ~= x0-1;
1045 return res;
1049 void fuzzyEnumerator () {
1050 import std.random;
1051 auto reg = Region(uniform!"[]"(1, 128), 1);
1052 int[] bmp, ebmp;
1053 bmp.length = reg.width*reg.height;
1054 ebmp.length = reg.width*reg.height;
1055 if (uniform!"[]"(0, 1)) {
1056 reg.rdata.simpleSolid = false;
1057 ebmp[] = 0; // default is empty
1058 } else {
1059 ebmp[] = 1; // default is solid
1061 foreach (immutable tx0; 0..1000) {
1062 checkLineOffsets(reg);
1063 buildBitmap(reg, bmp[]);
1064 debug(region_more_prints) {
1065 if (1/*bmp[] != ebmp[]*/) {
1066 assert(bmp.length == ebmp.length);
1067 writeln;
1068 foreach (immutable idx; 0..bmp.length) write(bmp[idx]); writeln;
1069 foreach (immutable idx; 0..ebmp.length) write(ebmp[idx]); writeln;
1072 assert(bmp[] == ebmp[]);
1073 foreach (immutable trx; 0..200) {
1074 //writeln("*");
1075 int x0 = uniform!"[)"(-10, reg.width+10);
1076 int x1 = uniform!"[)"(-10, reg.width+10);
1077 if (x0 > x1) { auto t = x0; x0 = x1; x1 = t; }
1078 int[] coords;
1079 int type = uniform!"[]"(0, 1);
1080 if (type == 0) {
1081 reg.spans!false(0, x0, x1, (int x0, int x1) { coords ~= x0; coords ~= x1; });
1082 } else {
1083 reg.spans!true(0, x0, x1, (int x0, int x1) { coords ~= x0; coords ~= x1; return 0; });
1085 auto ecr = buildCoords(bmp[], type, x0, x1);
1086 assert(ecr[] == coords[]);
1087 // now check enumerator range
1088 coords.length = 0;
1089 if (type == 0) {
1090 foreach (ref pair; reg.spanRange!false(0, x0, x1)) { coords ~= pair.x0; coords ~= pair.x1; }
1091 } else {
1092 foreach (ref pair; reg.spanRange!true(0, x0, x1)) { coords ~= pair.x0; coords ~= pair.x1; }
1094 if (ecr[] != coords[]) {
1095 import std.stdio : writeln;
1096 writeln("\ntype=", type);
1097 writeln("ecr=", ecr);
1098 writeln("crd=", coords);
1100 assert(ecr[] == coords[]);
1102 // now do random punch/patch
1104 int x = uniform!"[)"(0, reg.width);
1105 int y = uniform!"[)"(0, reg.height);
1106 int w = uniform!"[]"(0, reg.width);
1107 int h = uniform!"[]"(0, reg.height);
1108 int patch = uniform!"[]"(0, 1);
1109 debug(region_more_prints) { import core.stdc.stdio : printf; printf(":x0=%d; x1=%d; w=%d; solid=%d\n", x, x+w-1, w, patch); }
1110 if (patch) reg.patch(x, y, w, h); else reg.punch(x, y, w, h);
1111 // fix ebmp
1112 foreach (int dy; y..y+h) {
1113 if (dy < 0) continue;
1114 if (dy >= reg.height) break;
1115 foreach (int dx; x..x+w) {
1116 if (dx < 0) continue;
1117 if (dx >= reg.width) break;
1118 ebmp[dy*reg.width+dx] = patch;
1126 //enum OneSeed = 1586553857;
1128 void main () {
1129 import iv.writer;
1130 import std.random;
1131 foreach (immutable trycount; 0..1000) {
1133 auto seed = unpredictableSeed;
1134 static if (is(typeof(OneSeed))) seed = OneSeed;
1135 rndGen.seed(seed);
1136 write("try: ", trycount, "; seed = ", seed, " ... ");
1138 fuzzyEnumerator();
1139 writeln("OK");
1140 static if (is(typeof(OneSeed))) break;