Libraryless. Click here for Pure Java version (6432L/37K).
| 1 | // Previously used class "Q" (#1000934) used 328 bytes when idle. | 
| 2 | // Now we're at 32 (factor 10+). | 
| 3 | // And we can evaporate this even further by merging the queue | 
| 4 | // with the object it belongs to. Probably we'll end up paying nothing | 
| 5 | // for an idle object while still enjoying the experience of being | 
| 6 | // able to start a thread instantly whenever needed. | 
| 7 | |
| 8 | sclass CompactQ extends Meta is AutoCloseable { | 
| 9 | |
| 10 | // Object size at this point: 12 bytes for Java + 4 bytes for Meta | 
| 11 | // (assuming you have 32 GB or less of Java RAM) | 
| 12 | // | 
| 13 | // So let's keep saving space. | 
| 14 | |
| 15 | // Step 1 - no name... well, no reserved name field, anyway. | 
| 16 | // Most queues can probably be identified from context. | 
| 17 | // if you want to give your event queue a name - put it in Meta | 
| 18 | // with these convenient procedures: | 
| 19 | |
| 20 |   S name() { ret or2((S) metaGet("name"), toString()); } | 
| 21 |   void name(S name) { metaSet(+name); } | 
| 22 | |
| 23 | // Object size now: Still 16 bytes unless you foolishly insist on a | 
| 24 | // custom queue name which you could definitely achieve in a much | 
| 25 | // more efficient way (hint _subclassing_). | 
| 26 | |
| 27 | // Now we do need to expend 4 more bytes. The items in the queue | 
| 28 | // have to go somewhere. But! | 
| 29 | |
| 30 | // No syncLinkedList or something - just an AppendableChain which | 
| 31 | // is a minimalist approach but above the honor level of elegance | 
| 32 | // and flexibility and absolutely not in the way in these circumstances. | 
| 33 | // It's all O(1) after all. | 
| 34 | // | 
| 35 | // All sync is done on the main object BTW. (CompactQ.this) | 
| 36 | |
| 37 | AppendableChain<Runnable> q; | 
| 38 | |
| 39 | // Object size now: 20b | 
| 40 | |
| 41 | // retired also goes to meta. In the end you don't even notice a | 
| 42 | // difference. It's just another setter and getter and they are | 
| 43 | // clearly not hard to make either. | 
| 44 | // | 
| 45 | // Of course, you will notice that we store "unretired" as null | 
| 46 | // (meaning no entry at all, saving all the space) instead of false; | 
| 47 | // even in the case that someone retires their event queue and then | 
| 48 | // _unretires_ it. | 
| 49 | // | 
| 50 | // In the end, one can say we are really really pedantic but we do | 
| 51 | // have a style. | 
| 52 | |
| 53 |   synchronized bool retired() { ret metaGet("retired") != null; } | 
| 54 |   synchronized void retired(bool b) { metaPut("retired", trueOrNull(b)); } | 
| 55 | |
| 56 | // Object size: still 20b. Miraculous! | 
| 57 | // | 
| 58 | // ...Here comes a good one. | 
| 59 | |
| 60 | // The "enter" field is an optional thread ownership marker, e.g. | 
| 61 | // for modules in the Java OS because otherwise thread control is | 
| 62 | // a nightmare. | 
| 63 | // | 
| 64 | // Well... it still kind of is. But I see the way forward with one | 
| 65 | // clean re-architecture that we should one day just do. We just need | 
| 66 | // central instances of thread managing. This cannot be decentra- | 
| 67 | // lized. | 
| 68 | // | 
| 69 | // Oh, and having slashed absolutely everything else, we keep this | 
| 70 | // one as an actual field. | 
| 71 | // | 
| 72 | // The field type is so a customer's enter procedure can do | 
| 73 | // whatever fancy things they fashion as long as they provide an | 
| 74 | // AutoCloseable that will be called for cleanup. | 
| 75 | |
| 76 | IF0<AutoCloseable> enter; | 
| 77 | |
| 78 | // New size: 24b | 
| 79 | |
| 80 | // We do want to know who is servicing us currently... | 
| 81 | |
| 82 | Thread thread; // 28b | 
| 83 | bool startingThread; | 
| 84 | |
| 85 | // ...and finally the biggest boolean ever brings us to 32 bytes. | 
| 86 | |
| 87 | // Optional monitoring | 
| 88 | |
| 89 | ifdef CompactQ_Stats | 
| 90 | new AtomicLong bouts; // how often we have stopped and started | 
| 91 | int biggestSize; // how long the queue was at the worst moment | 
| 92 | endifdef | 
| 93 | |
| 94 |   AutoCloseable enter() { ret callF(enter); } | 
| 95 | |
| 96 |   synchronized bool running() { ret thread != null; } | 
| 97 | |
| 98 |   bool done() { ret !running() && (isEmpty() || retired() || !licensed()); } | 
| 99 | |
| 100 | // This is way smarter than the half-awake sleep(1) method | 
| 101 | // Basically we take a place at the end of the line and when we're | 
| 102 | // at the counter we see if anyone came in behind us. And repeat | 
| 103 | // if necessary. | 
| 104 | // | 
| 105 | // Note that for this to work reliably we require that items in | 
| 106 | // the queue are auto-closed in case they can't be dispatched | 
| 107 |   void waitUntilDone { | 
| 108 | while (!done()) | 
| 109 | putFlagInPipe(); | 
| 110 | } | 
| 111 | |
| 112 |   void putFlagInPipe() { | 
| 113 | flagSleep(flag -> add(raiseFlagOnRunAndClose(flag))); | 
| 114 | } | 
| 115 | |
| 116 | // Yes, in JavaX functions can have any number of names | 
| 117 |   public synchronized void close aka cancel() { | 
| 118 | retired(true); | 
| 119 | clear(); | 
| 120 | if (thread == null) ret; | 
| 121 | // threadBeingCancelled = new WeakReference(thread); // optional | 
| 122 | cancelAndInterruptThread(thread); | 
| 123 | thread = null; | 
| 124 | } | 
| 125 | |
| 126 | // Forget all the entries but don't cancel or interrupt currently | 
| 127 | // executing task | 
| 128 |   public void clear { | 
| 129 | L<Runnable> items; | 
| 130 |     synchronized { | 
| 131 | items = cloneList(q); | 
| 132 | q = null; | 
| 133 | } | 
| 134 | cleanUp(items); | 
| 135 | } | 
| 136 | |
| 137 | // append to q (do later) | 
| 138 |   void add(Runnable r) { | 
| 139 | if (r == null) ret; | 
| 140 |     synchronized { | 
| 141 | // cleanly reject if necessary | 
| 142 | if (retired()) ret with cleanUp(r); | 
| 143 | |
| 144 | q = chainPlus(q, r); | 
| 145 | ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef | 
| 146 | } | 
| 147 | checkForAction(); | 
| 148 | } | 
| 149 | |
| 150 | // prepend to q (do next) | 
| 151 |   void addInFront(Runnable r) { | 
| 152 | if (r == null) ret; | 
| 153 |     synchronized { | 
| 154 | // cleanly reject if necessary | 
| 155 | if (retired()) ret with cleanUp(r); | 
| 156 | |
| 157 | q = itemPlusChain(r, q); | 
| 158 | ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef | 
| 159 | } | 
| 160 | checkForAction(); | 
| 161 | } | 
| 162 | |
| 163 |   protected void checkForAction() { | 
| 164 |     synchronized { | 
| 165 | if (done() || running() || startingThread) | 
| 166 | ret; | 
| 167 | // There seems to be work, so we start a thread. | 
| 168 | set startingThread; | 
| 169 | } | 
| 170 | |
| 171 |     try { | 
| 172 | // At this point, we are no longer synchronized, but | 
| 173 | // we know we are uncontested. | 
| 174 | // No one else will try to start a thread. | 
| 175 | temp enter(); | 
| 176 |       startThread(name(), () -> { | 
| 177 |         try { | 
| 178 | temp enter(); | 
| 179 | |
| 180 | // Put us in the book. | 
| 181 |           synchronized { | 
| 182 | startingThread = false; | 
| 183 | thread = currentThread(); | 
| 184 | } | 
| 185 | |
| 186 | // And off we go! | 
| 187 | _bout(); | 
| 188 |         } finally { | 
| 189 | thread = null; | 
| 190 | startingThread = false; | 
| 191 | } | 
| 192 | }); | 
| 193 |     } on fail { | 
| 194 | close(); | 
| 195 | } | 
| 196 | } | 
| 197 | |
| 198 |   synchronized Runnable _grabWork() { | 
| 199 | Runnable r = first(q); | 
| 200 | q = popFirst(q); | 
| 201 | ret r; | 
| 202 | } | 
| 203 | |
| 204 | // work as long there is work | 
| 205 |   void _bout { | 
| 206 | ifdef CompactQ_Stats inc(bouts); endifdef | 
| 207 |     while (licensed() && !retired()) { | 
| 208 | Runnable r = _grabWork(); | 
| 209 | if (r != null) | 
| 210 |         pcall { r.run(); } | 
| 211 |       else { | 
| 212 | onIdle(); | 
| 213 | |
| 214 | // Try once more because onIdle may have strategically waited | 
| 215 | // for 1 ms or something to see if more work came in. | 
| 216 | |
| 217 | if ((r = _grabWork()) != null) | 
| 218 |           pcall { r.run(); } // get back in the game | 
| 219 | else | 
| 220 | break; | 
| 221 | } | 
| 222 | } | 
| 223 | } | 
| 224 | |
| 225 |   synchronized bool isEmpty() { ret q == null; } | 
| 226 |   synchronized int size() { ret l(q); } | 
| 227 | |
| 228 |   O mutex() { this; } // clients can synchronize on this | 
| 229 | |
| 230 | // export procedure following JavaX synchronization rules | 
| 231 | // (only synchronize on one object at a time. exceptions granted | 
| 232 | // when proof of harmlessness is given.) | 
| 233 |   synchronized L<Runnable> snapshot() { ret cloneList_unsynced(q); } | 
| 234 | |
| 235 | // you can override this | 
| 236 |   void onIdle {} | 
| 237 | } | 
Began life as a copy of #1000934
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
| Snippet ID: | #1032857 | 
| Snippet name: | CompactQ - Q (thread-based queue) but in compact - only 32 bytes when idle (including Java header!) | 
| Eternal ID of this version: | #1032857/76 | 
| Text MD5: | 32da1580b4fb6dbd8109b9d1b1e8aaa0 | 
| Transpilation MD5: | 8972134aa4e72bed0ed2fa022e28a3a7 | 
| Author: | stefan | 
| Category: | javax | 
| Type: | JavaX fragment (include) | 
| Public (visible to everyone): | Yes | 
| Archived (hidden from active list): | No | 
| Created/modified: | 2021-10-19 21:48:36 | 
| Source code size: | 7139 bytes / 237 lines | 
| Pitched / IR pitched: | No / No | 
| Views / Downloads: | 623 / 1069 | 
| Version history: | 75 change(s) | 
| Referenced in: | [show references] |