Reimagining Some of the Engine ...

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Reimagining Some of the Engine ...

Post by Baker »

Got kind of tired of making modifications to the Quake engine, mostly because getting to do anything "fun" can be too time consuming get cool results. You have to re-write everything and it kind of limits experimentation speed ...

Every operating system has its own file structure and Vista/Win 7 and beyond don't like writing to the application path. And it doesn't make sense to write intentional user stuff in the same place as potential engine temp files (i.e. I download a map or a install a mod, that isn't the same as my config ... I can re-download Travail, but I can't re-download a demo I made).

Code: Select all

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// These are the subfolders used by the engine on Windows
	#define ENGINE_WIN32_CACHES_SUBPATH	"caches"
	#define ENGINE_WIN32_DATA_SUBPATH	"Gamedata"

	char binaryPath[MAX_OSPATH_IS_1024];
	
	// Stage 1: Finalize versioning names
	Host_SetIdentity			(System_OS_Version());

	// Stage 2: Finalize path names
	StringLCopy (binaryPath, System_GetBinaryFullPath());
	String_Edit_Remove_FileName (binaryPath);

	Host_SetDirectories (
		/* Binary path */		System_GetBinaryFullPath(),
		/* Input data path */	va ("%s/%s", binaryPath, ENGINE_WIN32_DATA_SUBPATH),
		/* Write docs path */	va ("%s/%s", System_GetAppDataPath (), host.appName),
		/* Cache write dir */	va ("%s/%s/%s", System_GetAppDataPath (), host.appName, ENGINE_WIN32_CACHES_SUBPATH)
	);

	// Stage 3: Wire it up

	// Fill in any Windows specific stuff here
	w32_plat.hInstance				= hInstance;
	w32_plat.registeredClassName		= host.appVersionString;
	w32_plat.hIcon					= LoadIcon (w32_plat.hInstance, MAKEINTRESOURCE (IDI_ICON1));	// ID1_ICON1 is defined in resource.h

	Host_Init ();		// Keep separate in case we need access to memory, messages, earlier.
	Host_Main_Loop ();	// Main loop
	Host_Shutdown ();	// Shut everything down

	return (0); // Success
}
And taking the Host operations, which really don't vary much and giving it a home ... this never belonged in sys_win.c to begin with ...

Code: Select all

void Host_Main_Loop (void)
{
	unsigned previous_time_milliseconds = System_Milliseconds (); // Note ... this will initialize time, which does not count as a subsystem at least at this time

	//	Main program loop
	while (!host.terminate)
	{
		Platform_Run_Loop_Events (); // Let system handle messages, decide to sleep, whatever

		host.time_milliseconds		= System_Milliseconds ();
		host.timeslice				= host.time_milliseconds - previous_time_milliseconds;
		previous_time_milliseconds	= host.time_milliseconds;

		Host_Frame (host.timeslice);
	}
}

And taking all the operating system specific crap, isolating it in one place ...

Code: Select all

typedef struct
{
	// Required to construct a window
	HINSTANCE	hInstance;
	HICON		hIcon;						// Window icon
	const char	*registeredClassName;		// Set to the application version string

	// Window extra details ...
	HWND		mainwindow;					// HWND for our window
	HDC			maindc;						// device context for our window
	HGLRC		mainRC;						// rendering context
} platform_t;

Throw everything that can possibly be system neutral, but global, into its own "haystack".

Code: Select all

typedef struct
{
	const char	*operatingSysText;	// Text description of operating system
	const char  *appName;					// Application name
	const char	*appVersion;				// The version string
	const char	*appVersionString;			// Application version name

	// All these directories are full operating system path ...
    const char	*binaryFileName;			// (Not fullpath)
	const char	*binaryDir;					// Path to the binary (yeah, it may not be an ".exe")
    const char	*bundleDir;					// Input. <Program Files/whatever> Data that the application needs. (pak files)
    const char	*documentsDir;				// Output. <My Documents Something>User created files (save games, configs, etc.)
	const char	*cacheDir;					// Temp files, downloaded maps and such.  <App Data something> Non-user created content directory

	// Display environment ... on Windows this is the desktop for the primary monitor
	int			deviceWidth;
	int			deviceHeight;
	int			deviceBitsPerPixel;
	int			deviceRefreshRate;
	float		deviceScale;

	// Display state info ...
	fbool		cl_isFullScreen;			// Are we displaying fullscreen (therefore we probably changed the display settings
	int			cl_width;					// Client width
	int			cl_height;					// Client height
	int			cl_bitsPerPixel;			// Client bits per pixel
	int			cl_refreshRate;				// Refresh rate in Hz
	char		cl_describeVideo[64];		// Description

	fRECT		windowArea;
	fRECT		clientArea;
	int			borderAreaWidth;
	int			borderAreaHeight;
	int			mouse_center_x;				// Center of window to keep mouse centered
	int			mouse_center_y;				// Center of window to keep mouse centered

	// Messaging state ...
	fbool		isAltTabCapable;			// If we have no video window, we can't ALT-TAB
	fbool		isMinimized;
	fbool		isActiveApp;
	fbool		isVideoSuspended;
	fbool		isSoundSuspended;

	// Generate start
	fbool		startup_complete;

	// Timing
	fbool		terminate;					// If set to true a normal "Quit" has been issued.  Finishes frame.  Exits ..
	unsigned	time_milliseconds;
	unsigned	timeslice;
	

} host_t;
This function in Quake is a hopeless mess. Some MHness in this. Rather insistent on sizing at any time. Note how the bordersize is known ...

Code: Select all

LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// MSDSN: If an application processes this message, it should return zero to continue 
	
	// check for input messages
	if (Win32_ReadInputMessages (hWnd, uMsg, wParam, lParam)) return 0;

	switch (uMsg)
	{
	case WM_ACTIVATE:		// WM_ACTIVATE: Sent to both the window being activated and the window being deactivated.
							Platform_Focus_Change (LOWORD(wParam) != WA_INACTIVE, (fbool)HIWORD(wParam));
							return 0;

							
	case WM_KILLFOCUS:		// WM_KILLFOCUS: Sent to a window immediately before it loses the keyboard focus. 
							if (host.cl_isFullScreen)
								ShowWindow (w32_plat.mainwindow, SW_SHOWMINNOACTIVE); // Minmize if fullscreen
							return 0;

							
	case WM_CLOSE:			// WM_CLOSE: Signal that a window/application should terminate.
							Host_Normal_Quit ();
							return 0;
							
	case WM_CREATE:			// WM_CREATE: After window created, but before is visible
	case WM_DESTROY:		// WM_DESTROY: Sent when a window immediately after removed from screen
	case WM_SYSCHAR:		// WM_SYSCHAR: keep Alt-Space from happening and any ALT + letter combination
							return 0;

	case WM_SIZE:			// WM_SIZE:	Sent to a window after its size has changed.
							Platform_Window_Update_Info ();
							Host_Render_Frame ();
							return 0;


	case WM_MOVE:			// WM_MOVE: Sent after a window has been moved.
							Platform_Window_Update_Info ();
							return 0;
							

	case WM_GETMINMAXINFO:	// WM_GETMINMAXINFO: Sent before size change; can be used to override default mins/maxs
							if (host.isAltTabCapable) // If we aren't ALT-TAB capable, we cannot resize.
							{
								MINMAXINFO *mmi = (MINMAXINFO *) lParam;

								mmi->ptMinTrackSize.x = 320 + host.borderAreaWidth;
								mmi->ptMinTrackSize.y = 200 + host.borderAreaHeight;
							}
							return 0;


	}

	// pass all unhandled messages to DefWindowProc
	return DefWindowProc (hWnd, uMsg, wParam, lParam);

}
More stuff to some degree shaped by looking at MH's code ... note that I'm not doing host.windowArea = some RECT but host.windowArea.width = some RECT.width, etc.

This is because I'm not going to use a Windows RECT to store the client area or the Window area. Those are not really Windows-specific pieces of information, so I use an fRECT where f = Fake but the same and I can use that data structure on anything. I also avoid qboolean data type because it is {false, true} and that is a C++ datatype. So I use {False, True}, which is annoying because I prefer "true and false" ... but "True and False" don't conflict with C++ and I do want to potentially be able to utilize C++ code if the situation fits, and if I use "true and false" ... a mess begins being unable to share structs.

Code: Select all

// Refresh rects and mouse center
// Called at window resize/move and window construction.

void Platform_Window_Update_Info (void)
{
	WINDOWINFO windowinfo;
	RECT twindowArea;
	
	windowinfo.cbSize = sizeof (WINDOWINFO);

	GetWindowRect (w32_plat.mainwindow, &twindowArea);
	GetWindowInfo (w32_plat.mainwindow, &windowinfo);

	host.windowArea.top		= twindowArea.top;
	host.windowArea.bottom	= twindowArea.bottom;
	host.windowArea.left	= twindowArea.left;
	host.windowArea.right	= twindowArea.right;


	host.clientArea.top		= windowinfo.rcClient.top;
	host.clientArea.bottom	= windowinfo.rcClient.bottom;
	host.clientArea.left	= windowinfo.rcClient.left;
	host.clientArea.right	= windowinfo.rcClient.right;

	host.mouse_center_x		= host.clientArea.left + (RECT_WIDTH(host.clientArea) >> 1);
	host.mouse_center_y		= host.clientArea.top + (RECT_HEIGHT(host.clientArea) >> 1);
	host.borderAreaWidth	= RECT_WIDTH(host.windowArea) - RECT_WIDTH(host.clientArea);
	host.borderAreaHeight	= RECT_HEIGHT(host.windowArea) - RECT_HEIGHT(host.clientArea);

	// Fill in the description

	host.cl_width			= windowinfo.rcClient.right - windowinfo.rcClient.left;
	host.cl_height			= windowinfo.rcClient.bottom - windowinfo.rcClient.top;

	SNPrintf (host.cl_describeVideo, sizeof(host.cl_describeVideo), "%i x %i %i bpp rate = %i Hz (%s)", host.cl_width, host.cl_height, host.cl_bitsPerPixel, host.cl_refreshRate, host.cl_isFullScreen ? "fullscreen": "windowed");

}
The beginnings of an "automatic shutdown" system ... the code works as-is. To hell with Sys_Error and Host_Error. There shouldn't be 2 of them. There should be Host_Error and it should just shutdown whatever is actually initialized.

Code: Select all

typedef	fbool (*func_begin) (void);
typedef	void (*func_end) (void);

typedef struct
{
	func_begin	Startup;
	func_end	Shutdown;
	fbool		initialized;
} subsystem_t;

subsystem_t subsystems [] =
{
	{ Platform_Init,			Platform_Shutdown,			False	},	// Gets desktop size
	{ Platform_Window_Create,	NULL,						False	},	// Sets up video
	{ Renderer_OpenGL_Init,		Renderer_OpenGL_Shutdown,	False	},	// Initializes OpenGL
};

int num_sub_systems = sizeof (subsystems) / sizeof(subsystems[0]);

// Initialize sub-systems
void Host_Init (void)
{
	int i;

	// Initialize each sub system
	for (i = 0; i < num_sub_systems; i ++)
	{
		subsystems[i].initialized = subsystems[i].Startup ();
//		if (subsystems[i].initialized)
//			Printf something					

	}

	host.startup_complete = True;

}

// Shutdown sub-systems
void Host_Shutdown (void)
{
	// Last in, first out ...	
	int i, j;
	
	for (j = 0; j < num_sub_systems; j ++)
	{
		i = (num_sub_systems - j) - 1;  // ie 10 - 0 - 1 = starts at 9 
		if (!subsystems[i].initialized || subsystems[i].Shutdown == NULL)
			continue;

		subsystems[i].Shutdown ();
//			Printf something
	}
}
What I really want to do is rapidly put this all together and then start doing geometry, collision, lighting, particle and prediction/networking experiments. It is too hard to do most of that with Quake because the number of changes required grows quickly out of control.

End blah ...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Re: Reimagining Some of the Engine ...

Post by leileilol »

Making WinQuake a bit more "Windows 95" like could be fun.

- Resizable display context (on GL this no doubt would be evil for reinitializing, but software should be fine and stretchable to anything above 320x200)
- A menu bar with options, video modes (ala Unreal and Jazz Jackrabbit 2)
- a custom title bar (ala Jazz Jackrbabit 2 and some other games)
i should not be here
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Reimagining Some of the Engine ...

Post by Baker »

Me personally, I'm trying to imagine a world where maintaining multi-platform projects -- in particular Quake (although I'm kinda focused on the GL side) isn't such a son of a bitch [trying to keep in mind what Spike said about Q3 system agnostic input queuing].

But yeah, I'd kill for one of your above in software rendering. Fruitz of Dojo Quake actually does that on a Mac (running 320x200 software mode in a larger sized window like 640 x 400 or 960 x 600, etc.)

/Wishes the software renderer code wasn't such obtuse spaghetti as software rendering is completely multiplatform neutral.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Re: Reimagining Some of the Engine ...

Post by r00k »

Once you start multi-platform support, this is a never ending task * 3.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Reimagining Some of the Engine ...

Post by Baker »

r00k wrote:Once you start multi-platform support, this is a never ending task * 3.
There is some truth in that.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply