Jumper.cs Example C# Code

[Home] Win32 Code Example


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
/*
	platform-win.c

	Implements platform.h facilities such that the operating 
	system and platform are hidden. Provides program entry,
	timing, logging, and window initialization.
To implement: General FILE I/O, Graphics Initialization,
	Sound Interface(DirectSound), "wallclock" time,
	winproc platform state

	(c) 2019 Andrew Dunetz
*/
#define WIN32_LEAN_AND_MEAN 
#include "windows.h"

#include "random.h"
#include "platform.h"
//#include "collections.h"
#include <stdio.h>

#define FREE_BAD_ALLOC_IS_ERROR
#define REALLOC_REQUIRE_FIND_PREVIOUS

/* implementation details of the logger so that it 
 * can be printed to, started up, written out to 
 * file, cleaned up, and shut down. */
struct win32Logger
{
	int on;
	struct {
		HWND hwnd;
		HANDLE buffer;
	} console;
	struct {
		HANDLE file;
	} logFile;
};

/* handles for the game's window class. */
struct win32Window 
{
	WNDCLASSEX wndclassex;
	HWND hwnd;
};

/* state of the actual platform. */
struct win32Platform
{
	int running;
	struct PlatformAllocInfo *allocs;
};

/* this performance counter contains an inUse flag so that
 * it can be safely used with a Pool datastructure.  */
struct win32PerfClock
{
	int inUse;
	union {
		LARGE_INTEGER perfTime;
		u64 count;
	};
};

struct // win32PerfClocks
{
	u32 clockCount;
	struct win32PerfClock *clocks;
}win32PerfClocks;

// Temporary Alloc Implementation
//   This is a facility to allocate small amounts of memory from a 
//   'scratch-pad' that gets reset every frame. For this reason, it
//   is unsafe to hold on to memory allocated from temp past the 
//   frame boundry. Even so, it is very useful for string manipulation
//   and procedure returns.
struct TemporaryStack//global temporary stack
{
	unsigned int size;
	char *base;
	char *cur;
};


LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// platform memory allocation functions
void *PlatformAlloc(size_t size, char *name);
void *PlatformRealloc(void *alloc, size_t size, char *name);
void PlatformFree(void *alloc);
void PlatformDumpAllocs();

void *TempAlloc(size_t size, char *nop);
void TempDealloc(void *alloc);
void TempClear();

struct win32Logger win32InitLogger();
void win32ShutDownLogger();
struct win32Window win32WindowCreate();
void win32MessagePump(struct win32Window window);
struct win32Platform win32PlatformInit();
void win32InitPerfPool(int poolSize);

u64 win32PerfFrequency = 0;
static struct win32Logger Logger;
static struct win32Platform platform;

struct MemoryProvider platformMemory = (struct MemoryProvider) 
{ PlatformAlloc, PlatformRealloc, PlatformFree };

struct MemoryProvider tempMem = (struct MemoryProvider)
{ TempAlloc, 0, TempDealloc};

// since tempStack is allocated from the platform using the platform allocator, 
//  it is init by WinMain after platform allocation is set up.
struct TemporaryStack tempStack;

int WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR ncmdshow, int showcmd)
{
	Logger = win32InitLogger();
	struct win32Window window = win32WindowCreate();
	ShowWindow(window.hwnd, SW_SHOW);
	struct win32Platform platform = win32PlatformInit();
	QueryPerformanceFrequency((LARGE_INTEGER*)&win32PerfFrequency);
	win32InitPerfPool(128);

	// init temp stack
	{
		size_t size = 1024*1024;
		void *base = PlatformAlloc(size, "Temporary Stack");
		tempStack = (struct TemporaryStack) 
		{
			.size = size,
			.base = base,
			.cur = base,
		};
	}


//	GameInit();
	while(platform.running)
	{
		win32MessagePump(window);
//		GameLoop();
	};
	win32ShutDownLogger();
	return 0;
}

// Technically, windows reserves the right to interupt your program at
//  any time to tell you about 'something' you need to deal with.
// It does that through a callback to this function.
//
// For many items, it is sufficient to call DefWindowProc() and let Windows do
//  whatever it thinks should be done. For others, like quiting, cleanup requires
//  us to go through some of our own steps.
LRESULT CALLBACK WindowProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{ 
	switch( Msg)
	{
		case WM_DESTROY: { } // FALLTHROUGH
		case WM_CLOSE: { } // FALLTHROUGH
		case WM_QUIT: 
		{ 
			platform.running = 0;
			win32ShutDownLogger();
			ExitProcess(0);
		} break;
		default:
		{ 
			return DefWindowProc(hwnd, Msg, wParam, lParam); 
		} break;
	}
}

// The allocations of the platform layer are kept track of in a simple linked list.
//  this returns the first allocation in the list.
struct PlatformAllocInfo *win32GetFirstAlloc()
{ return platform.allocs; }

// Meanwhile, this overwrites that allocation. Meant be used internally, it 
// DOES NOT rewire the linked list, it only performs the assignment. As such,
// if used naively, it will break the allocator.
void win32SetFirstAlloc(struct PlatformAllocInfo* info)
{ platform.allocs = info; }

/* This ID is for debug reporting purposes only! Do not use it
		gameplay or system dependent code! */
u64 win32GenMemID()
{
	static u64 id = 0;
	return id++;
}

void *PlatformAlloc(size_t size, char *name)
{
	size_t trueSize = size + sizeof(struct PlatformAllocInfo);
	void *address = VirtualAlloc( 0, trueSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	void *pointer = (void*)((char*)address + sizeof(struct PlatformAllocInfo));
	*(struct PlatformAllocInfo*)address = (struct PlatformAllocInfo) 
	{
		.name = name,
		.id = win32GenMemID(),
		.next = win32GetFirstAlloc(),
		.size = trueSize
	};
	win32SetFirstAlloc(address);
	return pointer;
}

// Realloc in this implementation is just an abstraction of Alloc, Copy, and Free. It is provided for
//  convienience but does not create any real performance benefit.
//
// The reason the memory block header is not added to the size in this function is that it redirects
//  through PlatformAlloc. So we don't want to add 2 headers by acceident.
void *PlatformRealloc(void *alloc, size_t size, char *name)
{
#if REALLOC_REQUIRE_FIND_PREVIOUS
	// Find the old allocation, this exists only for debug purposes and does not 
	//  actually do anything.
	int foundAlloc = 0;
	struct PlatformAllocInfo * allocInfo = win32GetFirstAlloc();
	while (allocInfo)
	{
		void *candidate = ((char*)allocInfo)+sizeof(struct PlatformAllocInfo);
		if ( candidate == alloc)
		{
			foundAlloc = 1;
			break;
		}
		allocInfo = allocInfo->next;
	}
	if (!foundAlloc) 
	{
		LogFormat("Error: Attempt to realloc %p, but that doesn't seem to be an alloc!\n", alloc);
		ExitProcess(1);
	}
#endif

	void *address =(void*)((char*)alloc - sizeof(struct PlatformAllocInfo));
	void *nalloc = PlatformAlloc(size,name);

	// Theoretically, realloc allows resizing TO a smaller size, so we may need to select the smaller
	//  size and only copy that amount.
	size_t oldSize = ((struct PlatformAllocInfo*)(address))->size - sizeof(struct PlatformAllocInfo);
	size_t minSize = (oldSize < size) ? oldSize : size;
	memcpy(nalloc, alloc,minSize) ;

	PlatformFree(alloc);

	return nalloc;
}

void PlatformFree(void *alloc)
{
	void *address =(void*)((char*)alloc - sizeof(struct PlatformAllocInfo));
	struct PlatformAllocInfo *info = win32GetFirstAlloc();
	struct PlatformAllocInfo *pinfo;

	if (address == info)
	{ win32SetFirstAlloc(info->next); }
	else
	{
		while(info != address && info->next)
		{
			pinfo = info;
			info = info->next;
		}
		if (pinfo)
		{
			if(info->next)
			{ pinfo->next = info->next; }
			else
			{ pinfo->next = 0;}
		}
	}

#ifdef FREE_BAD_ALLOC_IS_ERROR
	// If we didn't find the address to free, crash.
	// (In production, we probably want to fail silently, but for debugging, we probably want to know if 
	// 	we're double freeing stuff.)
	if (info != address)
	{
		LogFormat("Error: Attempted to free address that is not recognized as a currently allocated block.\n");
		ExitProcess(1);
	}
#endif
	VirtualFree(address, 0, MEM_RELEASE);
}

// print out all open allocations
void PlatformDumpAllocs()
{
	int count = 0;
	LogFormat("Platform Allocations\n");
	struct PlatformAllocInfo *alloc = win32GetFirstAlloc();
	if (alloc)
	{
		void *address =(void*)((char*)alloc + sizeof(struct PlatformAllocInfo));
		while (alloc)
		{
			count++;
			LogFormat("\t%s(%llu) %p(%p)[%lu]\n", alloc->name, alloc->id, alloc, address, alloc->size);
			alloc = alloc->next;
		}
	}
	LogFormat("%i Allocations\n", count);
}

struct win32Platform win32PlatformInit()
{
	struct win32Platform platform = (struct win32Platform)
	{
		.running = 1
	};
	return platform;
}

// process every message that has been queued by windows for us to handle.
void win32MessagePump(struct win32Window window)
{
	MSG msg;
	while( PeekMessageA( &msg, window.hwnd, 0, 0, 1	))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

struct win32Window win32WindowCreate(HINSTANCE inst)
{
	struct win32Window window;
	WNDCLASSEX wndclass = 
	{
		.cbSize = sizeof(WNDCLASSEX),
		.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
		.lpfnWndProc = WindowProc,
		.cbClsExtra = 0,
		.cbWndExtra = 0,
		.hInstance = inst,
		.hIcon = 0,
		.hCursor = 0,
		.hbrBackground = 0,
		.lpszMenuName = 0,
		.lpszClassName = "Game",
		.hIconSm = 0
	};
	RegisterClassEx(&wndclass);
	HWND hwnd = CreateWindowEx(
		0, 
		"Game",
		"Game",
		0, //	WS_MAXIMIZE
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		0,
		0,
		inst,
		0
	);
	window = (struct win32Window) 
	{
		.hwnd = hwnd,
		.wndclassex = wndclass
	};
	return window;
}

// A windows EXE must choose whether to be a console or window application.
//  obviously, we want to be a window application. But we also want to be 
//  able to output to console. This function creates a console as a another
//  process and connects it to us. It also opens a file where we can print
//  everything we log.
struct win32Logger win32InitLogger()
{
	// create the console
	AllocConsole();
	HWND consoleHandle = GetConsoleWindow();
	HANDLE screenbuffer = CreateConsoleScreenBuffer(
		GENERIC_READ|GENERIC_WRITE,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		0,
		CONSOLE_TEXTMODE_BUFFER,
		0);
	SetConsoleActiveScreenBuffer(screenbuffer);
	DWORD mode;
	GetConsoleMode(consoleHandle, &mode);

	// open a log file
	HANDLE loggerHandle = CreateFile("game.log",GENERIC_READ|GENERIC_WRITE,
	FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL| FILE_FLAG_SEQUENTIAL_SCAN, 0);
	if (loggerHandle == INVALID_HANDLE_VALUE)
	{ ExitProcess(1); }

	return (struct win32Logger) 
	{
		.on = 1,
		.console.hwnd = consoleHandle,
		.console.buffer = screenbuffer,
		.logFile.file = loggerHandle
	};
}

// provide printf functionaliy in logging by routing through vsprintf_s
void LogFormat(char *string, ...)
{
	// if the logger is off, return
	if (!Logger.on)
	{return;}

	va_list va;
	va_start(va, string);
	char *buf[256];
	vsprintf_s((char*)buf, 256, string, va);
	buf[255] = 0;
	va_end(va);
	Log((char*)buf);
}

void Log(char *string)
{
	// If the logger is off, return
	if (!Logger.on)
	{return;}

	// Get the length of the string, because windows functions want it.
	size_t len = strlen(string);
	// Windows functions output the written count to a pointer.
	//  This variable is a destination for that.
	DWORD written = 0;
	// Write to the console
	WriteConsole(Logger.console.buffer, string, len, &written, 0);
	// Write to the file
	WriteFile( Logger.logFile.file, string, len, &written, 0);
	// Write to Visual Studio's weird psuedo-console thing
	OutputDebugStringA(string);
}

void LogSetOn(int on)
{ Logger.on = on; }

void win32ShutDownLogger()
{
	LogFormat("Shutting Down Logger\n");
	CloseHandle(Logger.console.buffer);
	SendMessage(Logger.console.hwnd, WM_QUIT,0,0);
	FreeConsole(); 
	CloseHandle(Logger.logFile.file);
}

// Performance Clock stuff
//  I am not decided on whether it makes sense to keep performance clocks 
//  in a pool, given their tiny footprint. In the past, I have added a lot
//  of functionality to perfclocks, so for the moment I'm going to keep this.
//
//  If I change my mind, I could rewrite this so that perfclocks can return by value
//  when created, removing the need for dynamic memory in the first place.

// create a pool of performance clocks to use
void win32InitPerfPool(int poolSize)
{
	win32PerfClocks.clockCount = poolSize;
	win32PerfClocks.clocks = VirtualAlloc( 0, poolSize*sizeof(struct win32PerfClock), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
	for (int i = 0; i < poolSize; i++)
	{ win32PerfClocks.clocks[i] = (struct win32PerfClock){0}; }
}

// get a performance clock from the pool
struct win32PerfClock *win32CheckoutPerfClock()
{
	for (int i = 0; i < win32PerfClocks.clockCount; i++)
	{
		if (!win32PerfClocks.clocks[i].inUse)
		{
			win32PerfClocks.clocks[i].inUse = 1;
			return win32PerfClocks.clocks + i;
		}
	}
	LogFormat("Error: Could not checkout win32 perf clock.\n");
	ExitProcess(1);
}

// give a preformance clock back to the pool
void win32ReleasePerfClock(struct win32PerfClock* clock)
{ clock->inUse = 0; }

void *PlatformStartPerfClock()
{
	struct win32PerfClock *clock = win32CheckoutPerfClock();
	QueryPerformanceCounter(&clock->perfTime);
	return clock;
}

r32 PlatfromEndPerfClock(void *clock)
{
	struct win32PerfClock *startClock = (struct win32PerfClock*)clock;
	u64 endCount = 0;
	QueryPerformanceCounter((LARGE_INTEGER*)&endCount);
	u64 diff = endCount - startClock->count;
	win32ReleasePerfClock(startClock);
	return (r32)diff/(r32)win32PerfFrequency;
}

void *TempAlloc(size_t size, char *nop)
{
	if (tempStack.cur+size > tempStack.base+tempStack.size)
	{
		LogFormat("TempAlloc ran out of memory!\n");
		ExitProcess(1);
	}
	char *n = tempStack.cur;
	tempStack.cur += size;
	return n;
}

void TempDealloc(void *alloc)
{ tempStack.cur = ((char*)alloc < tempStack.cur) ? alloc : tempStack.cur; }

void TempClear()
{ tempStack.cur = tempStack.base; }