Jump to content
  • entries
    51
  • comments
    106
  • views
    31,142

Parsing, Saving, Window Creation


reepblue

1,597 views

 Share

Before we were all locked in our homes, I was working on a lot of code for Leadwerks 4 for a reboot of concept I've made back in 2017. While I decided to shelf it again to wait for the new engine (Because the game will run faster, look better, VR, etc) I'm going to share you some of my core code which is cross compatible between the new and old engine. I'm sharing this as I see most people just using stock example code for window creation and setting saving without knowing how it all works. This is more of a show than tell kind of article, and most things are just copy and paste what's in my code at the moment, but it should give you an idea. In this article, I'll be showing you code for both Leadwerks 4 and 5, but most of this was written and tested in Leadwerks 4. 

 

Parsing Arguments.

Leadwerks 4 has a simple command that does this automatically. This makes it so you can give the app flags on how it should startup/behave on startup. 

System::ParseCommandLine(argc, argv);

In Leadwerks 5, you need to store it in your own std::map.

auto commandline = ParseCommandLine(argc, argv);

To be honest, the new way is much better. You see, in Leadwerks 4, System::ParseCommandLine() stored the values in a engine defined std::map which is also associated with System::LoadSettings() and System::SaveSettings(). This will cause unwanted results as flags you may only want to happen only when called in a batch file will save to the cfg file saved by default to the AppData folder in Windows. There is a separate map for both Arguments and Settings, but I think the issue is with if you use System::Set/GetProperty().

The best option is to keep your arguments and setting separate. In Leadwerks 5, your std::map is yours, so you're already off to a good start.

 

Actual Settings

Now we can take in flags/arguments from launch settings, we now need values to be written on the disk. First we need to build the directory in which will act as our data folder for the user. Where this folder should be is up to you, but it should be in the user's directory on Windows, or the user's home directory in Linux. Here's my code for both Leadwerks 4 and 5. 

First, we need to know the applications name. We use this name value in multiple areas. Leadwerks 4 as a value System::AppName, but in Leadwerks 5, we'll be creating our own variable. We'll also define the settings directory string path here too.

#ifndef LEADWERKS_5
//std::string AppName = "MyGame";
std::string settingsdir = "";
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void App::SetName(const std::string& pszAppName)
{
	System::AppName = pszAppName;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
std::string App::GetName()
{
	return System::AppName;
}

#else
std::wstring AppName = L"MyGame";
std::wstring settingsdir = L"";

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void App::SetName(std::wstring pszAppName)
{
	AppName = pszAppName;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
std::wstring App::GetName()
{
	return AppName;
}
#endif

 

Now we have a name variable, we can use it to build our settings directory. 

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildSettingsDirectory()
{
#ifndef LEADWERKS_5
	// If settingsdir is not "", we did this already, no need to do it again.
	if (settingsdir != "")
		return;

#if defined (_WIN32)
	auto t = FileSystem::GetDocumentsPath() + "/" + App::GetName();
#else
	auto t = FileSystem::GetAppDataPath() + "/." + String::Lower(App::GetName());
#endif

	if (FileSystem::GetFileType(t) == 0)
	{
		System::Print("Building Settings folder under:" + t);
		FileSystem::CreateDir(t);
	}

	settingsdir = t;
#else
	// If settingsdir is not "", we did this already, no need to do it again.
	if (settingsdir != L"")
		return;

#if defined (_WIN32)
	auto t = FolderLocation(FOLDER_DOCUMENTS) + L"/" + AppName;
#else
	auto t = FolderLocation(FOLDER_APPDATA) + L"/." + Lower(AppName);
#endif

	if (FileType(t) == 0)
	{
		Print(L"Building Settings folder under:" + t);
		CreateDir(t);
	}

	settingsdir = t;
#endif
}

 

You'll now have the path to your settings directory stored in settingsdir. You can use this string to store files there easily like below: (Raw copy and paste, ignore my macros)

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void App::TakeScreenShot()
{
	auto framebuffer = Framebuffer::GetCurrent();
	if (framebuffer != NULL)
	{
		auto screenshot_path = GetSettingsPath() + "/Screenshots";
		if (FileSystem::GetFileType(screenshot_path) == 0)
		{
			Msg("Building Screenshots folder under:" + screenshot_path);
			FileSystem::CreateDir(screenshot_path);
		}

		framebuffer->Screenshot(screenshot_path + "/" + App::GetName() + "_" + Timing::GetCurrentDate() + ".tga");
	}
	else
	{
		Msg("Error: Failed to take screenshot.");
	}
}

 

A long time ago, @Crazycarpet gave me code to parse a text file like a Leadwerks 4 config file or a Leadwerks 4 mat file. I turned that functionality into a class, and instead of having the engine phrase the information, I do this myself. This is a direct copy and paste of my scopes to give you an idea.

// To Load
auto settingspath = settingsdir + "/settings.cfg";
if (FileSystem::GetFileType(settingspath) == 0)
{
	appsettings.fullscreen = false;
	appsettings.screenwidth = 1280;
	appsettings.screenheight = 720;
	appsettings.display = 0;

	appsettings.antialias = 1; //2
	appsettings.lightquality = 1;
	appsettings.terrainquality = 1;
	appsettings.texturequality = Texture::GetDetail();
	appsettings.waterquality = 1;
	appsettings.afilter = Texture::GetAnisotropy();
	appsettings.verticalsync = false;
	appsettings.hdrmode = true;
}
else
{
	auto config = LoadConfig(settingspath);
	appsettings.fullscreen = config->GetBoolValue("fullscreen", false);
	appsettings.screenwidth = config->GetIntValue("screenwidth", 800);
	appsettings.screenheight = config->GetIntValue("screenheight", 600);
	appsettings.display = config->GetIntValue("display", 0);

	appsettings.antialias = config->GetIntValue("antialias", 1);
	appsettings.lightquality = config->GetIntValue("lightquality", 1);
	appsettings.terrainquality = config->GetIntValue("terrainquality", 1);
	appsettings.texturequality = config->GetIntValue("texturequality", Texture::GetDetail());
	appsettings.waterquality = config->GetIntValue("waterquality", 1);
	appsettings.afilter = config->GetIntValue("afilter", Texture::GetAnisotropy());
	appsettings.verticalsync = config->GetBoolValue("verticalsync", false);
	appsettings.hdrmode = config->GetBoolValue("hdrmode", true);

	config = NULL;
}

//---------------------//

// To Save
auto config = CreateConfig(settingspath);
config->WriteKeyValue("fullscreen", to_string(appsettings.fullscreen)); 
config->WriteKeyValue("screenwidth", to_string(appsettings.screenwidth));
config->WriteKeyValue("screenheight", to_string(appsettings.screenheight));
config->WriteKeyValue("display", to_string(appsettings.display));

config->WriteKeyValue("antialias", to_string(appsettings.antialias));
config->WriteKeyValue("lightquality", to_string(appsettings.lightquality));
config->WriteKeyValue("terrainquality", to_string(appsettings.terrainquality));
config->WriteKeyValue("texturequality", to_string(appsettings.texturequality));
config->WriteKeyValue("waterquality", to_string(appsettings.waterquality));
config->WriteKeyValue("afilter", to_string(appsettings.afilter));
config->WriteKeyValue("tfilter", to_string(appsettings.tfilter));
config->WriteKeyValue("verticalsync", to_string(appsettings.verticalsync));
config->WriteKeyValue("hdrmode", to_string(appsettings.hdrmode));

bool b = config->WriteOut();
config = NULL;
return b;

 

Settings vs Arguments

Since both settings and arguments are different tables, we must have a way to check if ether the setting or argument is enabled. The best way I chose to do this is to make bool functions that check if ether is true.

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool App::IsFullScreen()
{
	if (appsettings.fullscreen == true || GetArgument("fullscreen") == "1")
		return true;

	return appsettings.fullscreen;
}

 

Building The Window (Finally)

Now it's time to actually build the window with the information we have via settings file, or augments passed in a command line. 

For Leadwerks 4, I do something like this:

Leadwerks::Window* CreateEngineWindow(std::string pszTileName, Leadwerks::Window* parent)
{
	// Are we fullscreen?
	const bool fullscreenmode = App::IsFullScreen();

	// Set the name of the app the same as the window.
	App::SetName(pszTileName);

	// Load Settings.
	App::LoadSettings();

	//Load any zip files in main directory
	Leadwerks::Directory* dir = Leadwerks::FileSystem::LoadDir(".");
	if (dir)
	{
		for (std::size_t i = 0; i < dir->files.size(); i++)
		{
			std::string file = dir->files[i];
			std::string ext = Leadwerks::String::Lower(Leadwerks::FileSystem::ExtractExt(file));
			if (ext == "zip" || ext == "pak")
			{
				Leadwerks::Package::Load(file, KEY);
			}
		}
		delete dir;
	}

	// Create a window
	Leadwerks::Window* window;
	std::string windowtitle = App::GetName();
	if (IsDebug()) windowtitle = windowtitle + " [Debug]";

	if (fullscreenmode)
	{
		iVec2 gfx = System::GetGraphicsMode(Leadwerks::System::CountGraphicsModes() - 1);
		window = Leadwerks::Window::Create(windowtitle, 0, 0, gfx.x, gfx.y, Leadwerks::Window::Fullscreen, parent);
	}
	else
	{
		window = Leadwerks::Window::Create(windowtitle, 0, 0, App::GetScreenWidth(), App::GetScreenHeight(), Leadwerks::Window::Titlebar | Leadwerks::Window::Center, parent);
	}

	EngineWindow::current = window;
	window->Activate();
	return window;
}

 

This is my current Leadwerks 5 code:

shared_ptr<Window> CreateEngineWindow(std::wstring pszTileName, shared_ptr<Window> parent)
{
	//Get the primary display
	auto display = App::GetDisplay();

	// Are we fullscreen?
	const bool fullscreenmode = App::IsFullScreen();

	// Set the name of the app the same as the window.
	App::SetName(pszTileName);

	// Load Settings.
	App::LoadSettings();

	// Create a window
	shared_ptr<Window> window;
	std::wstring windowtitle = App::GetName();
	if (IsDebug()) windowtitle = windowtitle + L" [Debug]";

	if (fullscreenmode)
	{
		auto gfxmodes = display->GraphicsModes();
		auto gfx = gfxmodes[gfxmodes.size() - 1];
		window = CreateWindow(display, App::GetName(), 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN, parent);
	}
	else
	{
		Vec2 displayscale = display->GetScale();
		window = CreateWindow(display, windowtitle, 0, 0, App::GetScreenWidth() * displayscale.x, App::GetScreenHeight() * displayscale.y, WINDOW_TITLEBAR | WINDOW_CENTER, parent);
	}

	return window;
}

 

As you might have picked up, I store all of the core app functionality in a singleton class called "App". This is what the header looks like to give you an idea.

class App 
{
public:

#ifndef LEADWERKS_5
	static void SetName(const std::string& pszAppName);
	static std::string GetName();
#else
	static void SetName(std::wstring pszAppName);
	static std::wstring GetName();
#endif

	static void ParseCommandLine(int argc, const char* argv[]);
	static bool CheckArgument(std::string pszKey);
	static std::string GetArgument(std::string pszKey);
	static int GetArgumentInt(std::string pszKey);
	static float GetArgumentFloat(std::string pszKey);
	std::map<std::string, std::string> Arguments();

	static bool LoadSettings();
	static bool SaveSettings();
	static AppSettings GetSettings();
#ifndef LEADWERKS_5
	static std::string GetSettingsPath();
#else
	static std::wstring GetSettingsPath();
#endif

	static void Shutdown();
	static bool Running();

	static void EnableVR();

	static bool IsDevMode();
	static bool IsFullScreen();
	static unsigned int GetScreenWidth();
	static unsigned int GetScreenHeight();
	static unsigned int GetDisplayIndex();

#ifdef LEADWERKS_5
	static shared_ptr<Display> GetDisplay();
#endif

	static unsigned int GetAntialias();
	static unsigned int GetLightingQuality();
	static unsigned int GetTerrainQuality();
	static unsigned int GetTextureQuality();
	static unsigned int GetWaterQuality();
	static unsigned int GetAnisotropicFilter();
	static bool GetTrilinearFilter();
	static bool GetVerticalSync();
	static bool GetHDRMode();

	static void TakeScreenShot();
};

 

Bonus: Load JSON Files Easily

Leadwerks 5 includes the json library included, but as of time of writing, there is no simple way to load the format into something usable. Josh shared his approach using the engine's Stream Class. The json library can also be included in Leadwerks 4 with little to no effort! This is a very nice format to have in your base app code.

#if defined (EOF)
#undef EOF
#endif

nlohmann::json ReadJSON(const std::string& pszFileName)
{
#ifndef LEADWERKS_5
	auto stream = FileSystem::ReadFile(pszFileName);
	auto path = FileSystem::RealPath(pszFileName);

	if (stream == NULL)
	{
		Print("Error: Failed to load JSON script \"" + path + "\"");
		//stream->path = L"";
		return NULL;
	}

	//Check for starting bracket
	bool started = false;
	while (!stream->EOF())
	{
		//auto c = stream->ReadLine();
		auto c = String::Chr(stream->ReadUChar());
		if (String::Trim(c) == "") continue;
		if (c != "{") return false;
		started = true;
		break;
	}
	if (!started) return false;
	stream->Seek(0);
#else
	auto stream = ReadFile(pszFileName);
	auto path = RealPath(pszFileName);

	if (stream == NULL)
	{
		Print(L"Error: Failed to load JSON script \"" + path + L"\"");
		//stream->path = L"";
		return NULL;
	}

	//Check for starting bracket
	bool started = false;
	while (!stream->Ended())
	{
		auto c = Chr(stream->ReadByte());
		if (Trim(c) == "") continue;
		if (c != "{") return NULL;
		started = true;
		break;
	}
	if (!started) return false;
	stream->Seek(0);
#endif

	std::vector<uint8_t> data;
	auto sz = stream->GetSize();
	data.resize(sz);
	stream->Read(data.data(), sz);

	nlohmann::json j3;
	try
	{
		j3 = nlohmann::json::parse(data);
	}
	catch (std::exception & e)
	{
		std::cout << e.what() << std::endl;
		return false;
	}
	if (j3.type() != nlohmann::json::value_t::object) return NULL;

#ifndef LEADWERKS_5
	stream->Release();
#endif
	stream = NULL;
	return j3;
}

#ifdef LEADWERKS_5
nlohmann::json WReadJSON(const std::wstring& pszFileName)
{
	auto stream = ReadFile(pszFileName);
	auto path = RealPath(pszFileName);

	if (stream == NULL)
	{
		Print(L"Error: Failed to load JSON script \"" + path + L"\"");
		//stream->path = L"";
		return NULL;
	}

	//Check for starting bracket
	bool started = false;
	while (!stream->Ended())
	{
		auto c = Chr(stream->ReadByte());
		if (Trim(c) == "") continue;
		if (c != "{") return NULL;
		started = true;
		break;
	}
	if (!started) return false;
	stream->Seek(0);

	std::vector<uint8_t> data;
	auto sz = stream->GetSize();
	data.resize(sz);
	stream->Read(data.data(), sz);

	nlohmann::json j3;
	try
	{
		j3 = nlohmann::json::parse(data);
	}
	catch (std::exception & e)
	{
		std::cout << e.what() << std::endl;
		return false;
	}
	if (j3.type() != nlohmann::json::value_t::object) return NULL;

	stream = NULL;
	return j3;
}
#endif

 

This type of code are things you can do right now in the Leadwerks 5 beta while you wait for the rest of the engine to come online. As for me, I already have this code done and this layer of stuff works flawlessly and produces the same results on both engines. If there is anything else you want me to share based on core.

  • Like 2
 Share

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...