Database integration as well as client/server functionality

I’ve been trying to integrate MySQL into my game’s server-side code, but I feel the way I’m doing it is lacking. I’m pretty sure some mutex/threading is needed here, but I don’t know if UE4 has such classes or if I should use std::thread/mutex. Additionally, for replicated variables that issue function calls, I want the client and the server to both execute different code from each other (ie. when a character levels up, the client displays a visual effect while the server updates a record in the database.

Although lengthy, I’m not posting my entire code here, but I need advice on all of this that I’m posting below. First, I started off modifying <project>.Build.cs to link MySQL if the server code is built:


public MyProject(TargetInfo Target)
    {
        // default module add code is here, snipped it out for irrelevancy to this thread post

        if (UEBuildConfiguration.bWithServerCode)
            LoadMySQL(Target);
    }

    public bool LoadMySQL(TargetInfo Target)
    {
        bool supported = false;

        if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32)
        {
            supported = true;
            string PlatformString = (Target.Platform == UnrealTargetPlatform.Win64) ? "Win64" : "Win32";
            string LibrariesPath = Path.Combine(ThirdPartyPath, "MySQL", "Libraries", PlatformString);
            PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, PlatformString, "libmysql.lib"));
	    PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, PlatformString, "mysqlclient.lib"));
        }

        if (supported)
            PublicIncludePaths.Add( Path.Combine( ThirdPartyPath, "MySQL", "Includes" ) );
        Definitions.Add(string.Format( "WITH_MYSQL={0}", supported ? 1 : 0 ));

        return supported;
    }

Then, I override the default game module implementation with my own if the server code is compiled, otherwise, FDefaultGameModuleImpl will do:

MyProject.h:


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"

#if WITH_SERVER_CODE
#include "my_global.h"
#include "mysql.h"

class FMyProjectGameModule : FDefaultGameModuleImpl
{
	MYSQL *world_db, *save_db;

public:
	MYSQL* GetWorldDB() const { return world_db; }
	MYSQL* GetSaveDB() const { return save_db; }
	virtual void StartupModule() override;
	virtual void ShutdownModule();
};

#endif // WITH_SERVER_CODE

MyProject.cpp:


// Fill out your copyright notice in the Description page of Project Settings.

#include "MyProject.h"

#if WITH_SERVER_CODE
#include <cstdio>

void FMyProjectGameModule::StartupModule() override
{
	std::FILE *configfile;
	char wdb_addr[255], wdb_user[32], wdb_pw[32], wdb_name[32];
	char sdb_addr[255], sdb_user[32], sdb_pw[32], sdb_name[32];
	unsigned short wdb_port, sdb_port;

	if (!(configfile = std::fopen("server.cfg", "r"))) // unsure of where 'server.cfg' will be found yet
	{
		UE_LOG(LogTemp, Fatal, TEXT("Unable to read server.cfg"));
		std::fclose(configfile);
	}

	std::fscanf("WorldDatabase=%s;%s%s;%s;%hu", wdb_addr, wdb_user, wdb_pw, wdb_name, &wdb_port);
	std::fscanf("SavesDatabase=%s;%s%s;%s;%hu", sdb_addr, sdb_user, sdb_pw, sdb_name, &sdb_port);
	std::fclose(configfile);

	if (!(world_db = mysql_init(NULL)))
	{
		UE_LOG(LogTemp, Fatal, TEXT("Unable to initialise the world database connection"));
		mysql_close(world_db);
	}

	if (!(save_db = mysql_init(NULL)))
	{
		UE_LOG(LogTemp, Fatal, TEXT("Unable to initialise the saves database connection"));
		mysql_close(save_db);
	}

	if (!(mysql_real_connect(world_db, wdb_addr, wdb_user, wdb_pw, wdb_name, wdb_port, NULL, 0)))
	{
		UE_LOG(LogTemp, Fatal, TEXT("Unable to connect to world database connection at %s:%hu"), wdb_addr, &wdb_port);
		mysql_close(world_db);
	}

	if (!(mysql_real_connect(save_db, sdb_addr, sdb_user, sdb_pw, sdb_name, sdb_port, NULL, 0)))
	{
		UE_LOG(LogTemp, Fatal, TEXT("Unable to connect to saves database connection at %s:%hu"), sdb_addr, &sdb_port);
		mysql_close(save_db);
	}
}

void FMyProjectGameModule::ShutdownModule()
{
	mysql_close(world_db);
	mysql_close(save_db);
}

IMPLEMENT_PRIMARY_GAME_MODULE(FMyProjectGameModule, MyProject, "MyProject");
#else
IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, MyProject, "MyProject");
#endif // WITH_SERVER_CODE

and lastly, I’m unsure of how to write a function that does somethiung different on client/server. I’m thinking “#if WITH_SERVER_CODE … #else#endif” but I have a feeling that’s not correct:

MyPlayerState.h (snippet):


...
UPROPERTY(replicatedusing = OnRep_Level, blueprintreadwrite, category = CharacterData)
uint8 Level;

UFUNCTION(server)
void OnRep_Level();
...

MyPlayerState.cpp (snippet):


...
void AMyPlayerState::OnRep_Level_Implementation()
{
	if (mysql_query(MyProject->GetSaveDB(), TCHAR_TO_UTF8(*FString.Printf("UPDATE `characters` SET `level`=%hhu WHERE `id`=%d", Level, PlayerId))))
		UE_LOG(LogTemp, Warning, TEXT("Failed to update level for character ID %d!"), PlayerId);
}
...

Also, I know I’m using FILE and fscanf as opposed to std::ifstream here. It’s probably not recommended, but I find reading formatted input from a config file preferrable over streamed input, given that I want something reminiscent of an .INI file.

Heya,

Thanks for this post! I’m curious about this too, and wonder if others have had experiences with integrating mysql connectivity into server. Our plan is to run our dedicated server on linux so there could be further complications with crosscompiling libraries.

In regard to threads, have a look at Rama’s guide at A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums

In regard to loading saved parameters, another Rama’s guide may be suitable (without the compression parts) A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums,Read%26_Write_Any_Data_to_Compressed_Binary_Files

Good luck!

urk… I cringe at class-derived threading. I was hoping it could be more like std::thread