Skip to content

Commit c08e698

Browse files
committed
Extremely fast D version; uses different algorithm, so not included in benchmark.
1 parent 7aff9e0 commit c08e698

File tree

1 file changed

+315
-0
lines changed

1 file changed

+315
-0
lines changed

MegaD.d

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// Compile with:
2+
// ldc2 -O2 -release -singleobj -wi -disable-boundscheck <file.d>
3+
4+
module deterministic;
5+
6+
import std.stdio, std.random, std.parallelism;
7+
8+
enum LEVEL_SIZE = 50; /// Width and height of a level
9+
enum ROOM_MIN = 3; /// Rooms will be at least this large.
10+
enum ROOM_MAX = 10; /// Rooms will be at most this large.
11+
12+
enum NUM_LEVS = 800;
13+
14+
alias ℕ = size_t;
15+
16+
// Helpers for bit counting.
17+
18+
/// 64-bit population count algorithm. For LDC the intrinsics have a good fallback if POPCNT doesn't exist.
19+
ℕ countBitsD(ℕ i) pure nothrow {
20+
version (LDC) {
21+
import ldc.intrinsics;
22+
return llvm_ctpop(i);
23+
} else static if (ℕ.sizeof == 4) {
24+
import core.bitop;
25+
return popcnt(i);
26+
} else {
27+
i = i - ((i >> 1) & 0x5555555555555555uL);
28+
i = (i & 0x3333333333333333uL) + ((i >> 2) & 0x3333333333333333uL);
29+
return (((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FuL) * 0x101010101010101uL) >> 56;
30+
}
31+
}
32+
33+
// For GDC we have to determine at runtime wether POPCNT is supported since the compiler
34+
// intrinsic has a slow fallback.
35+
version(GNU) {
36+
__gsharedfunction(ℕ) countBits = &countBitsD;
37+
38+
shared static this() {
39+
import core.cpuid;
40+
if (hasPopcnt) {
41+
countBits = function(ℕ i) pure nothrow {
42+
import gcc.builtins;
43+
static if (ℕ.sizeof == 4) {
44+
return __builtin_popcount(i);
45+
} else {
46+
return __builtin_popcount(i & uint.max) + __builtin_popcount(i >> 32);
47+
}
48+
};
49+
}
50+
}
51+
} else {
52+
alias countBits = countBitsD;
53+
}
54+
55+
// Room and level generation structures
56+
57+
struct Room {
58+
ℕ x, y, w, h;
59+
}
60+
61+
template LevelGen(ℕ levelSize, ℕ roomMin, ℕ roomMax) {
62+
/// The number of different room widths/heights.
63+
enum roomSizes = roomMax + 1 - roomMin;
64+
/// Number of positions for the smallest room on one axis.
65+
enum minRoomSlots = levelSize - 1 - roomMin;
66+
/// Number of positions for the largest room on one axis.
67+
enum maxRoomSlots = levelSize - 1 - roomMax;
68+
/// Number of possibilities to place different rooms along one axis.
69+
enum optionsPerAxis = (minRoomSlots + maxRoomSlots) * roomSizes / 2;
70+
/// Number of bits in a machine word.
71+
enum wordBits = ℕ.sizeof * 8;
72+
/// Set if internal representations of the levels always fit into a machine word. (Minor optimization.)
73+
enum singleWord = minRoomSlots < wordBits;
74+
75+
/**
76+
* Returns at compile-time a lookup table that maps room sizes to how many placement
77+
* options exist for them on an empty level.
78+
*/
79+
enum initialAvailableOptionCntsPerRoom() {
80+
ℕ[roomSizes][roomSizes] result;
81+
foreach (w; roomMin .. roomMax + 1) {
82+
foreach (h; roomMin .. roomMax + 1) {
83+
immutable cnt = cast(uint) ((levelSize - 1 - w) * (levelSize - 1 - h));
84+
result[w - roomMin][h - roomMin] = cnt;
85+
}
86+
}
87+
return result;
88+
}
89+
90+
struct Level {
91+
/// The maximum number of rooms that can be in a single level.
92+
enum roomLimit = ((levelSize - 1) / (roomMin + 1)) ^^ 2;
93+
94+
Room[roomLimit] rooms;
95+
ℕ roomCnt = 0;
96+
ubyte[levelSize][levelSize] tiles;
97+
98+
void dump() const @trusted {
99+
foreach (row; 0 .. levelSize) {
100+
foreach (col; 0 .. LEVEL_SIZE) {
101+
write( this.tiles[row][col] ? " " : "██" );
102+
}
103+
writeln();
104+
}
105+
}
106+
}
107+
108+
enum blockedArraySize() {
109+
ℕ words = 0;
110+
foreach (w; maxRoomSlots .. minRoomSlots + 1) {
111+
words += (w + wordBits - 1) / wordBits;
112+
}
113+
return words * optionsPerAxis;
114+
}
115+
116+
enum calcBlockedOffsets() {
117+
ℕ[roomSizes][roomSizes] result;
118+
ℕ offset = 0;
119+
foreach (h; roomMin .. roomMax + 1) {
120+
immutable hSlots = levelSize - 1 - h;
121+
foreach (w; roomMin .. roomMax + 1) {
122+
result[w - roomMin][h - roomMin] = offset;
123+
immutable wWords = (levelSize - 1 - w + wordBits - 1) / wordBits;
124+
offset += hSlots * wWords;
125+
}
126+
}
127+
return result;
128+
}
129+
130+
enum initialBlockedPositions() {
131+
ℕ[blockedArraySize()] result;
132+
ℕ offset = 0;
133+
foreach (h; roomMin .. roomMax + 1) {
134+
immutable hSlots = levelSize - 1 - h;
135+
foreach (w; roomMin .. roomMax + 1) {
136+
immutable wWords = (levelSize - 1 - w + wordBits - 1) / wordBits;
137+
immutable strideBits = (levelSize - 1 - w) % wordBits;
138+
if (strideBits != 0) {
139+
immutable strideMask = ℕ.max << strideBits;
140+
auto pos = &result[offset + wWords - 1];
141+
foreach (y; 0 .. levelSize - 1 - h) {
142+
*pos = strideMask;
143+
if (y != levelSize - 2 - h) {
144+
pos += wWords;
145+
}
146+
}
147+
}
148+
offset += hSlots * wWords;
149+
}
150+
}
151+
return result;
152+
}
153+
154+
struct RoomGenerator {
155+
/// Total number of possibilities to place any kind of room on an empty level.
156+
enum totalOptionCnt = optionsPerAxis * optionsPerAxis;
157+
158+
/**
159+
* The options are ordered as follows:
160+
* On the highest order are room sizes, smallest room first, then increasing width, then increasing height.
161+
* For every room there is a bitmap with all available positions for this room size.
162+
*/
163+
ℕ[blockedArraySize()] blocked = initialBlockedPositions();
164+
/// Offsets into the 'blocked' array corresponding to given room sizes.
165+
immutable static ℕ[roomSizes][roomSizes] blockedOffsets = calcBlockedOffsets();
166+
/// All currently available room positions. This is the sum of 'availableOptionCntsPerRoom'.
167+
ℕ availableOptionCnt = totalOptionCnt;
168+
/// Available room positions broken down by room sizes.
169+
ℕ[roomSizes][roomSizes] availableOptionCntsPerRoom = initialAvailableOptionCntsPerRoom();
170+
171+
/**
172+
* Retrieves a rectangular area from 'blocked' that is reserved for the possible positions of rooms of
173+
* size w*h.
174+
*/
175+
* bitmapForRoomDims(ℕ w, ℕ h) {
176+
return &this.blocked[this.blockedOffsets[w - roomMin][h - roomMin]];
177+
}
178+
179+
/// Places the n-th option of rooms sized w*h in the level.
180+
void placeRoom(ℕ w, ℕ h, ℕ option, ref Level level) {
181+
const(ℕ*) bitmap = this.bitmapForRoomDims(w, h);
182+
const(ℕ)* bitptr = bitmap;
183+
immutable stride = (levelSize - 1 - w + wordBits - 1) / wordBits;
184+
185+
while (wordBits - countBits(*bitptr) <= option) {
186+
option -= wordBits - countBits(*bitptr++);
187+
}
188+
189+
ℕ bit = 0;
190+
while (option) {
191+
if (!(*bitptr & (1uL << bit++))) {
192+
option--;
193+
}
194+
}
195+
while (*bitptr & (1uL << bit)) {
196+
bit++;
197+
}
198+
immutable x = (bitptr - bitmap) % stride * wordBits + bit;
199+
immutable y = (bitptr - bitmap) / stride;
200+
this.placeRoom(x, y, w, h, level);
201+
}
202+
203+
/// Used internally by the other overload, to place a room at a known good position.
204+
void placeRoom(ℕ x, ℕ y, ℕ w, ℕ h, ref Level level) {
205+
this.makeSlotsUnavailable(x, y, x + w, y + h);
206+
level.rooms[level.roomCnt++] = Room(x, y, w, h);
207+
foreach (ly; y + 1 .. y + h + 1) {
208+
foreach (lx; x + 1 .. x + w + 1) {
209+
level.tiles[ly][lx] = 1;
210+
}
211+
}
212+
}
213+
214+
/**
215+
* Given an occluded area, this function marks room positions as unavailable where the room would
216+
* overlap the area. This also updates the probabilities for each room size and the total available
217+
* room positions count 'availableOptionCnt'.
218+
*/
219+
void makeSlotsUnavailable(ℕ x1, ℕ y1, ℕ x2, ℕ y2) {
220+
foreach (h; roomMin .. roomMax + 1) {
221+
foreach (w; roomMin .. roomMax + 1) {
222+
// Don't try to mark any squares as blocked if there are none left.
223+
if (this.availableOptionCntsPerRoom[w - roomMin][h - roomMin] == 0)
224+
continue;
225+
226+
auto bitmap = this.bitmapForRoomDims(w, h);
227+
// Calculate actual affected area for this room size.
228+
ℕ rx1 = (x1 > w) ? x1 - w : 0;
229+
ℕ ry1 = (y1 > h) ? y1 - h : 0;
230+
ℕ rx2 = (x2 + 1 < levelSize - 1 - w) ? x2 + 1 : levelSize - 1 - w;
231+
ℕ ry2 = (y2 + 1 < levelSize - 1 - h) ? y2 + 1 : levelSize - 1 - h;
232+
233+
// Disable all options in this area.
234+
ℕ removedOptions = 0;
235+
immutable stride = (levelSize - 1 - w + wordBits - 1) / wordBits;
236+
immutable initialMask = ℕ.max << (rx1 % wordBits);
237+
immutable finalMask = ~(ℕ.max << (rx2 % wordBits));
238+
if (singleWord || rx1 / wordBits == rx2 / wordBits) {
239+
// All bits are in the same word.
240+
immutable mask = initialMask & finalMask;
241+
foreach (y; ry1 .. ry2) {
242+
auto wordptr = &bitmap[stride * y + rx1 / wordBits];
243+
removedOptions += countBits(~*wordptr & mask);
244+
*wordptr |= mask;
245+
}
246+
} else {
247+
foreach (y; ry1 .. ry2) {
248+
// initial
249+
auto wordptr = &bitmap[stride * y + rx1 / wordBits];
250+
removedOptions += countBits(~*wordptr & initialMask);
251+
*wordptr++ |= initialMask;
252+
ℕ x = (rx1 / wordBits + 1) * wordBits;
253+
// central
254+
while (x + wordBits <= rx2) {
255+
removedOptions += countBits(~*wordptr);
256+
*wordptr++ = ℕ.max;
257+
x += wordBits;
258+
}
259+
// final
260+
if (x < rx2) {
261+
removedOptions += countBits(~*wordptr & finalMask);
262+
*wordptr |= finalMask;
263+
}
264+
}
265+
}
266+
this.availableOptionCntsPerRoom[w - roomMin][h - roomMin] -= removedOptions;
267+
this.availableOptionCnt -= removedOptions;
268+
}
269+
}
270+
}
271+
}
272+
}
273+
274+
// Main function
275+
276+
alias MyLevelGen = LevelGen!(LEVEL_SIZE, ROOM_MIN, ROOM_MAX);
277+
278+
// Global variables are thread local in D.
279+
Random perThreadRng;
280+
281+
static this() {
282+
// Per thread module ctor.
283+
perThreadRng = Random(unpredictableSeed);
284+
}
285+
286+
void main() @system {
287+
MyLevelGen.Level[NUM_LEVS] levels;
288+
289+
// Process levels in parallel with a batch size of 1 level.
290+
foreach (i, ref level; parallel(levels[], 1)) {
291+
MyLevelGen.RoomGenerator rg;
292+
while (rg.availableOptionCnt) {
293+
/* Respecting the current potential success of placing a room of a certain size,
294+
we pick a width and height. */
295+
ℕ option = uniform(0, rg.availableOptionCnt);
296+
ℕ w, h;
297+
SizeSearch:
298+
for (h = 0; h < MyLevelGen.roomSizes; h++) {
299+
for (w = 0; w < MyLevelGen.roomSizes; w++) {
300+
if (option < rg.availableOptionCntsPerRoom[w][h]) {
301+
break SizeSearch;
302+
}
303+
option -= rg.availableOptionCntsPerRoom[w][h];
304+
}
305+
}
306+
w += ROOM_MIN;
307+
h += ROOM_MIN;
308+
309+
rg.placeRoom(w, h, option, level);
310+
}
311+
}
312+
313+
// Print one of the levels.
314+
levels[0].dump();
315+
}

0 commit comments

Comments
 (0)