1 /**
2 Copyright: Copyright (c) 2015-2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module server;
7 
8 import std.experimental.logger;
9 import std.array;
10 import std.algorithm;
11 import std.exception;
12 import std.range;
13 import std.math;
14 import std.conv;
15 import std.string;
16 
17 import storage;
18 import chunkmanager;
19 
20 alias Timestamp = uint;
21 alias BlockId = ubyte;
22 alias ClientId = uint;
23 
24 // 1D version of engine for test
25 enum CHUNK_SIZE = 16;
26 
27 struct HashSet(K) {
28 	private void[0][K] set;
29 
30 	void put()(auto ref K key) {
31 		set[key] = (void[0]).init;
32 	}
33 
34 	bool remove()(auto ref K key) {
35         return set.remove(key);
36     }
37 
38     size_t length() const @property {
39         return set.length;
40     }
41 
42     @property bool empty() const {
43         return set.length == 0;
44     }
45 
46     bool opCast(T: bool)() const {
47         return !empty;
48     }
49 
50     bool opBinaryRight(string op)(auto ref K key) const if(op == "in") {
51         return cast(bool)(key in set);
52     }
53 
54     void clear() {
55     	set = null;
56     }
57 
58     auto items() @property {
59     	return set.byKey;
60     }
61 }
62 
63 struct BlockWorldPos {
64 	this(int blockWorldPos){
65 		pos = blockWorldPos;
66 	}
67 	this(float blockWorldPos) {
68 		pos = cast(int)blockWorldPos;
69 	}
70 	int pos;
71 	string toString() @safe {return to!string(pos);}
72 }
73 
74 struct BlockChunkPos {
75 	this(BlockWorldPos blockWorldPos) {
76 		int ipos = blockWorldPos.pos % CHUNK_SIZE;
77 		if (ipos < 0) ipos += CHUNK_SIZE;
78 		pos = cast(ubyte)ipos;
79 	}
80 	this(ubyte blockChunkPos) {
81 		pos = blockChunkPos;
82 	}
83 	ubyte pos;
84 	string toString() @safe {return to!string(pos);}
85 }
86 
87 // Position of chunk in world space. -int.max..int.max
88 struct ChunkWorldPos {
89 	this(BlockWorldPos blockWorldPos) {
90 		pos = cast(int)floor(cast(float)blockWorldPos.pos / CHUNK_SIZE);
91 	}
92 	this(int chunkWorldPos) {
93 		pos = chunkWorldPos;
94 	}
95 	int pos;
96 	string toString() @safe {return to!string(pos);}
97 }
98 
99 struct BlockChange {
100 	BlockChunkPos index;
101 	BlockId blockId;
102 }
103 
104 struct ChunkDataSnapshot {
105 	BlockId[] blocks;
106 	Timestamp timestamp;
107 	uint numUsers;
108 }
109 
110 struct Client {
111 	string name;
112 	ClientId id;
113 	Volume1D viewVolume;
114 	void sendChunk(ChunkWorldPos pos, const ubyte[] chunkData){}
115 	void sendChanges(ChunkWorldPos cwp, BlockChange[] changes){
116 		infof("changes @%s %s", cwp, changes);
117 	}
118 	void delegate(Server* server) sendDataToServer;
119 }
120 
121 struct ChunkFreeList {
122 	BlockId[][] items;
123 	size_t numItems;
124 
125 	BlockId[] allocate() {
126 		if (numItems > 0) {
127 			--numItems;
128 			return items[numItems];
129 		} else {
130 			return new BlockId[CHUNK_SIZE];
131 		}
132 	}
133 
134 	void deallocate(BlockId[] blocks) {
135 		if (items.length < numItems) {
136 			items[numItems] = blocks;
137 		} else {
138 			items.reserve(32);
139 			items ~= blocks;
140 		}
141 	}
142 }
143 
144 struct Set(T) {
145 	T[] items;
146 	alias items this;
147 
148 	bool contains(T item) {
149 		return canFind(items, item);
150 	}
151 
152 	// returns true if already has one
153 	bool put(T item) {
154 		if (!contains(item)) {
155 			items ~= item;
156 			return false;
157 		} else
158 			return true;
159 	}
160 
161 	void remove(T item) {
162 		T[] items;
163 		items = std.algorithm.remove!(a => a == item, SwapStrategy.unstable)(items);
164 	}
165 }
166 
167 final class Server {
168 	Timestamp currentTime;
169 	ChunkInMemoryStorage inmemStorage;
170 	ChunkManager chunkManager;
171 	ChunkObserverManager chunkObserverManager;
172 	WorldAccess worldAccess;
173 
174 	Client*[ClientId] clientMap;
175 
176 	this()
177 	{
178 		chunkManager = new ChunkManager();
179 		chunkManager.onChunkLoadedHandlers ~= &onChunkLoaded;
180 		chunkManager.chunkChangesHandlers ~= &sendChanges;
181 		chunkManager.loadChunkHandler = &inmemStorage.loadChunk;
182 		chunkManager.saveChunkHandler = &inmemStorage.saveChunk;
183 		inmemStorage.onChunkLoadedHandlers ~= &chunkManager.onSnapshotLoaded;
184 		inmemStorage.onChunkSavedHandlers ~= &chunkManager.onSnapshotSaved;
185 		chunkObserverManager = new ChunkObserverManager();
186 		chunkObserverManager.changeChunkNumObservers = &chunkManager.setExternalChunkUsers;
187 		worldAccess = new WorldAccess(&chunkManager);
188 	}
189 
190 	void preUpdate() {
191 		// Advance time
192 		++currentTime;
193 		inmemStorage.update();
194 	}
195 
196 	void update() {
197 		// logic. modify world, modify observers
198 	}
199 
200 	void postUpdate() {
201 		chunkManager.commitSnapshots(currentTime);
202 		chunkManager.sendChanges();
203 
204 		// do regular save
205 		// chunkManager.save(currentTime);
206 	}
207 
208 	void save() {
209 		chunkManager.save();
210 	}
211 
212 	void sendChanges(ChunkWorldPos cwp, BlockChange[] changes) {
213 		foreach(clientId; chunkObserverManager.getChunkObservers(cwp)) {
214 			clientMap[clientId].sendChanges(cwp, changes);
215 		}
216 	}
217 
218 	void onChunkLoaded(ChunkWorldPos cwp, ChunkDataSnapshot snap) {
219 		infof("LOAD @%s", cwp);
220 		foreach(clientId; chunkObserverManager.getChunkObservers(cwp)) {
221 			clientMap[clientId].sendChunk(cwp, snap.blocks);
222 		}
223 	}
224 
225 	void onClientConnected(Client* client) {
226 		infof("CONN '%s'", client.name);
227 		clientMap[client.id] = client;
228 		chunkObserverManager.addObserver(client.id, client.viewVolume);
229 	}
230 
231 	void onClientDisconnected(Client* client) {
232 		infof("DISC '%s'", client.name);
233 		chunkObserverManager.removeObserver(client.id);
234 	}
235 
236 	bool setBlock(BlockWorldPos bwp, BlockId blockId) {
237 		return worldAccess.setBlock(bwp, blockId);
238 	}
239 
240 	BlockId getBlock(BlockWorldPos bwp) {
241 		return worldAccess.getBlock(bwp);
242 	}
243 }