1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE a_modes.inc}
17 {.$DEFINE IDPOOL_CHECKS}
22 // ////////////////////////////////////////////////////////////////////////// //
24 //TODO: implement getting n sequential ids
25 TIdPool
= class(TObject
)
27 const InvalidId
= $ffffffff;
31 TRange
= packed record
32 first
, last
: LongWord;
36 mRanges
: array of TRange
; // available ids; sorted
37 mRangeUsed
: Integer; // used elements in `mRanges`
42 function findRangeWithId (aid
: LongWord): Integer;
44 function getHasFreeId (aid
: LongWord): Boolean;
45 function getHasAllocedId (aid
: LongWord): Boolean;
47 function getFreeIds (): Integer; inline;
48 function getCapacity (): Integer; inline;
51 constructor Create (amax
: LongWord=$7fffffff);
52 destructor Destroy (); override;
56 // returns InvalidId if there are no more free ids (or throws)
57 function alloc (dothrow
: Boolean=true): LongWord;
59 // returns InvalidId if there are no more free ids (or throws)
60 function alloc (aid
: LongWord; dothrow
: Boolean=true): LongWord;
62 // it is NOT ok to release already released id
63 procedure release (aid
: LongWord);
69 property hasFree
[aid
: LongWord]: Boolean read getHasFreeId
;
70 property hasAlloced
[aid
: LongWord]: Boolean read getHasAllocedId
;
71 property maxId
: LongWord read mMaxId
;
73 property usedIds
: Integer read mUsedIds
;
74 property freeIds
: Integer read getFreeIds
;
76 property usedRanges
: Integer read mRangeUsed
;
77 property capacity
: Integer read getCapacity
;
86 // ////////////////////////////////////////////////////////////////////////// //
87 constructor TIdPool
.Create (amax
: LongWord=$7fffffff);
89 if (amax
= InvalidId
) then amax
:= InvalidId
-1;
95 destructor TIdPool
.Destroy ();
102 procedure TIdPool
.dump ();
106 writeln('=== idpool: ', mRangeUsed
, ' ranges ===');
107 for f
:= 0 to mRangeUsed
-1 do
109 writeln(' #', f
, ': [', mRanges
[f
].first
, '-', mRanges
[f
].last
, ']');
110 if (mRanges
[f
].last
+1 = mRanges
[f
].first
) then raise Exception
.Create('unmerged ranges');
111 if (f
> 0) and (mRanges
[f
-1].last
>= mRanges
[f
].first
) then raise Exception
.Create('invalid range order');
112 if (f
> 0) and (mRanges
[f
-1].last
+1 = mRanges
[f
].first
) then raise Exception
.Create('unmerged ranges');
114 writeln('-----------');
118 procedure TIdPool
.check ();
122 for f
:= 0 to mRangeUsed
-1 do
124 if (mRanges
[f
].first
> mRanges
[f
].last
) then begin dump(); raise Exception
.Create('invalid range'); end;
125 if (mRanges
[f
].first
> mMaxId
) then begin dump(); raise Exception
.Create('invalid range'); end;
126 if (mRanges
[f
].last
> mMaxId
) then begin dump(); raise Exception
.Create('invalid range'); end;
127 if (f
> 0) and (mRanges
[f
-1].last
>= mRanges
[f
].first
) then begin dump(); raise Exception
.Create('invalid range order'); end;
128 if (f
> 0) and (mRanges
[f
-1].last
+1 = mRanges
[f
].first
) then begin dump(); raise Exception
.Create('unmerged ranges'); end;
133 procedure TIdPool
.clear ();
135 SetLength(mRanges
, 64);
136 mRanges
[0].first
:= 0;
137 mRanges
[0].last
:= mMaxId
;
143 function TIdPool
.getFreeIds (): Integer; inline; begin result
:= Integer(mMaxId
+1-mUsedIds
); end;
144 function TIdPool
.getCapacity (): Integer; inline; begin result
:= Length(mRanges
); end;
147 function TIdPool
.findRangeWithId (aid
: LongWord): Integer;
149 len
, bot
, mid
, i
: Integer;
153 if (aid
> mMaxId
) then exit
;
156 if (len
<= 0) then exit
;
157 if (len
= 1) then begin result
:= 0; exit
; end;
158 // yay! use binary search to find the range
163 mid
:= i
-(i
-bot
) div 2;
164 //!assert((mid >= 0) and (mid < len));
165 ls
:= mRanges
[mid
].first
;
166 le
:= mRanges
[mid
+1].first
;
167 if (aid
>= ls
) and (aid
< le
) then begin result
:= mid
; exit
; end; // i found her!
168 if (aid
< ls
) then i
:= mid
-1 else bot
:= mid
;
174 function TIdPool
.getHasFreeId (aid
: LongWord): Boolean; inline;
179 if (aid
> mMaxId
) then exit
;
180 ii
:= findRangeWithId(aid
);
181 if (ii
< 0) then exit
;
182 result
:= (aid
>= mRanges
[ii
].first
) and (aid
<= mRanges
[ii
].last
);
186 function TIdPool
.getHasAllocedId (aid
: LongWord): Boolean; inline;
191 if (aid
> mMaxId
) then exit
;
192 ii
:= findRangeWithId(aid
);
193 if (ii
>= 0) then result
:= not ((aid
>= mRanges
[ii
].first
) and (aid
<= mRanges
[ii
].last
)) else result
:= true;
197 // returns InvalidId if there are no more free ids (or throws)
198 function TIdPool
.alloc (dothrow
: Boolean=true): LongWord;
202 if (mRangeUsed
= 0) then
205 if dothrow
then raise Exception
.Create('TIdPool: no more free ids');
209 result
:= mRanges
[0].first
;
210 // delete first range?
211 if (mRanges
[0].last
= result
) then
213 for c
:= 1 to mRangeUsed
-1 do mRanges
[c
-1] := mRanges
[c
];
218 Inc(mRanges
[0].first
);
221 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
225 // returns InvalidId if there are no more free ids (or throws)
226 function TIdPool
.alloc (aid
: LongWord; dothrow
: Boolean=true): LongWord;
230 if (mRangeUsed
= 0) then
233 if dothrow
then raise Exception
.Create('TIdPool: no more free ids');
238 if (aid
> mMaxId
) then
240 if dothrow
then raise Exception
.Create('TIdPool: cannot allocate invalid id');
244 // find range with this id
245 ii
:= findRangeWithId(aid
);
246 if (ii
< 0) or (aid
< mRanges
[ii
].first
) or (aid
> mRanges
[ii
].last
) then
248 if dothrow
then raise Exception
.Create('TIdPool: cannot allocate already allocated id');
252 // always return requested id
254 // can we shrink range head?
255 if (aid
= mRanges
[ii
].first
) then
257 // yep; range with the only id?
258 if (aid
= mRanges
[ii
].last
) then
261 for c
:= ii
+1 to mRangeUsed
-1 do mRanges
[c
-1] := mRanges
[c
];
266 mRanges
[ii
].first
:= aid
+1;
269 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
272 // can we shrink range tail?
273 if (aid
= mRanges
[ii
].last
) then
275 // yep; simply shrink, 'cause range with one id was processed in the previous `if`
276 mRanges
[ii
].last
:= aid
-1;
278 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
281 // split this range to two
282 if (mRangeUsed
+1 > Length(mRanges
)) then SetLength(mRanges
, Length(mRanges
)+1024);
283 for c
:= mRangeUsed
downto ii
+1 do mRanges
[c
] := mRanges
[c
-1];
285 mRanges
[ii
].last
:= aid
-1;
286 mRanges
[ii
+1].first
:= aid
+1;
288 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
292 // it is NOT ok to release already released id
293 procedure TIdPool
.release (aid
: LongWord);
297 if (aid
> mMaxId
) then raise Exception
.Create(Format('TIdPool: cannot release invalid id %u', [aid
]));
299 if (mRangeUsed
= 0) then
301 // just create new range
302 if (Length(mRanges
) = 0) then SetLength(mRanges
, 64);
303 mRanges
[0].first
:= aid
;
304 mRanges
[0].last
:= aid
;
307 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
310 // before first available id?
311 if (aid
< mRanges
[0].first
) then
313 // can we grow first range?
314 if (aid
+1 = mRanges
[0].first
) then
317 mRanges
[0].first
:= aid
;
321 // nope, insert new first range
322 if (mRangeUsed
+1 > Length(mRanges
)) then SetLength(mRanges
, Length(mRanges
)+1024);
323 assert(mRangeUsed
< Length(mRanges
));
324 for c
:= mRangeUsed
downto 1 do mRanges
[c
] := mRanges
[c
-1];
326 mRanges
[0].first
:= aid
;
327 mRanges
[0].last
:= aid
;
330 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
333 // after last available id?
334 if (aid
> mRanges
[mRangeUsed
-1].last
) then
336 // can we grow last range?
337 if (aid
-1 = mRanges
[mRangeUsed
-1].last
) then
340 mRanges
[mRangeUsed
-1].last
:= aid
;
344 // nope, insert new last range
345 if (mRangeUsed
+1 > Length(mRanges
)) then SetLength(mRanges
, Length(mRanges
)+1024);
346 assert(mRangeUsed
< Length(mRanges
));
347 mRanges
[mRangeUsed
].first
:= aid
;
348 mRanges
[mRangeUsed
].last
:= aid
;
352 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
355 // alas, no more easy cases; find the nearest range
356 ii
:= findRangeWithId(aid
);
357 if (ii
< 0) then raise Exception
.Create(Format('TIdPool: cannot release invalid id %u', [aid
]));
358 if (aid
>= mRanges
[ii
].first
) and (aid
<= mRanges
[ii
].last
) then raise Exception
.Create(Format('TIdPool: cannot release unallocated id %u', [aid
]));
359 // ii should contain range where `first` is less than `aid`
360 assert(mRanges
[ii
].first
< aid
);
361 assert(mRanges
[ii
].last
< aid
);
362 {$IF DEFINED(IDPOOL_DEBUG_DUMPS)}writeln('aid=', aid
, '; ii=', ii
, ': [', mRanges
[ii
].first
, '-', mRanges
[ii
].last
, ']');{$ENDIF}
363 // can grow this range at the end?
364 if (mRanges
[ii
].last
+1 = aid
) then
366 {$IF DEFINED(IDPOOL_DEBUG_DUMPS)}writeln(' endgrow');{$ENDIF}
367 // yep; can merge ranges?
368 if (ii
+1 < mRangeUsed
) and (aid
+1 = mRanges
[ii
+1].first
) then
371 {$IF DEFINED(IDPOOL_DEBUG_DUMPS)}writeln(' endmerge');{$ENDIF}
372 mRanges
[ii
].last
:= mRanges
[ii
+1].last
;
373 for c
:= ii
+2 to mRangeUsed
do mRanges
[c
-1] := mRanges
[c
];
379 {$IF DEFINED(IDPOOL_DEBUG_DUMPS)}writeln(' endchange');{$ENDIF}
380 {$IF DEFINED(IDPOOL_DEBUG_DUMPS)}if (ii
+1 < mRangeUsed
) then writeln(' ii+1=', ii
+1, ': [', mRanges
[ii
+1].first
, '-', mRanges
[ii
+1].last
, ']');{$ENDIF}
381 mRanges
[ii
].last
:= aid
;
384 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
387 // can grow next range at the start?
388 if (ii
+1 < mRangeUsed
) and (aid
+1 = mRanges
[ii
+1].first
) then
390 // yep; can merge ranges?
391 if (mRanges
[ii
].last
+1 = mRanges
[ii
+1].first
) then
394 mRanges
[ii
].last
:= mRanges
[ii
+1].last
;
395 for c
:= ii
+2 to mRangeUsed
do mRanges
[c
-1] := mRanges
[c
];
401 mRanges
[ii
+1].first
:= aid
;
404 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}
407 // cannot grow anything, insert empty range after ii
408 if (mRangeUsed
= Length(mRanges
)) then SetLength(mRanges
, Length(mRanges
)+1024);
409 for c
:= mRangeUsed
downto ii
do mRanges
[c
+1] := mRanges
[c
];
411 mRanges
[ii
].first
:= aid
;
412 mRanges
[ii
].last
:= aid
;
415 {$IF DEFINED(IDPOOL_CHECKS)}check();{$ENDIF}