Code Monkey home page Code Monkey logo

unet's Introduction

Unet

Unified Networking for lobbies. Provides a single unified lobby using multiple service lobbies. This can be useful to make cross-platform play possible, as well as fallbacks to other services if one service gets disconnected.

Supported services

The following services and APIs are currently supported:

  • Steam P2P
  • GOG Galaxy P2P
  • Enet P2P

Usage

Initializing Unet and creating a lobby:

#include <Unet.h>

class MyCallbacks : public Unet::ICallbacks
{
	virtual void OnLobbyCreated(const CreateLobbyResult &result) override
	{
		printf("Lobby created!\n");
	}
}

int main()
{
	// ... initialize Steam/Galaxy/Enet here ...

	// Create the Unet context
	Unet::IContext* ctx = Unet::CreateContext();

	// Prepare context callbacks
	ctx->SetCallbacks(new MyCallbacks);

	// Initialize the services we'll be using (the first EnableService call will be the "primary" service)
	ctx->EnableService(Unet::ServiceType::Steam);
	ctx->EnableService(Unet::ServiceType::Galaxy);
	ctx->EnableService(Unet::ServiceType::Enet);

	// Create a lobby
	ctx->CreateLobby(Unet::LobbyPrivacy::Public);

	// Main loop
	while (true) {
		// Run callbacks and keep the context updated
		ctx->RunCallbacks();

		// ... do your polling/updating/rendering here ...
	}

	// Leave the lobby if we're still in it
	ctx->LeaveLobby();

	// Destroy the Unet context
	Unet::DestroyContext(ctx);
	return 0;
}

For a complete example, check out the cli folder for the Unet CLI application. Most important header files are fully documented, so you should be able to set up your project via that pretty easily.

Building

Unet uses GENie to build. To include GENie into your own projects, you can choose to either build it yourself first in this repository, or by using GENie in your project yourself, which is probably the easiest.

If you're using GENie, you have to do something like this in your build scripts:

dofile(UNET_DIR .. 'genie/genie_unet.lua')
unet_project({
	modules = {
		steam = {
			link = true,
			dir = STEAMWORKS_DIR
		},
		galaxy = {
			link = true,
			dir = GOG_DIR
		},
		enet = {
			link = true,
			dir = ENET_DIR
		}
	}
})

Notice that Steam, Galaxy, and Enet are modules. You may decide to include them or leave them, if you don't support them. You must pass the path in which the SDK for the service is in dir, and specify in link whether we should link to the SDK or not.

Games that use Unet

If you're using Unet, let me know, and I'll add you to this list!

Technical info

Internally, each service sends packets on 3 or more separate channels.

  • Channel 0: Internal lobby control channel. All "internal" lobby data resides here. The transferred data are all encoded json objects.
  • Channel 1: Relay channel. Used when clients want to send packets to clients that don't share a service. Same as general purpose data, except starts with the destination peer ID and desired channel.
  • Channel 2 and up: General purpose channels.

These channels are entirely separate from the public Context SendTo API. There, channel 0 is transferred internally on channel 2, channel 1 on channel 3, etc.

Quirks

Below I'm listing some fun quirks I found out.

  • When hosting a lobby yourself, searching for lobbies will not result in your own lobby showing up in the lobby list on Galaxy, but it does on Steam.
  • While Steam's reliable packets have a send limit of 1 MB (actually 1024 * 1024), Galaxy's reliable packets don't seem to have a limit at all. It does get slower the more MB you send though, even on a gigabit network. (In my tests, transfer rate is roughly around 2 MB/s)

unet's People

Contributors

codecat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

suruz

unet's Issues

Optimize reliable packet reassembly

Due to the fact that we can't really know if a packet was sent reliably or unreliably, we have to use a single bit in the sequence ID to check if it was sent reliably or not.

There's a couple places that have to optimize the packet flow:

  • We can get an early receive of unreliable messages before callbacks are ran in Context::ReadMessage (also avoiding allocation of NetworkMessage objects there), and just return the message. The only problem is that we can't possibly know if a message is reliable or not in Context::IsMessageAvailable, so maybe this point can't realistically be done?
  • We can avoid allocating NetworkMessage objects on the heap in Context::ReadMessage if we just return the object by value instead of reference.

Optionally, we can also make some sort of service-specific flag system. This will for example help us bypass all of the issues raised with reliable packet size limits re-assembly by simply returning 0 in ServiceSteam::ReliablePacketLimit.

Allow cancelling joins

There's a couple problems with this:

First of all, Steam and Galaxy don't allow cancelling joins, which means the only possible way to "cancel" is by waiting for the join to succeed, and then immediately leave the lobby. This is definitely not ideal.

Secondly, this needs to be implemented as some kind of queue, in order to allow sequences like these:

  1. Join
  2. Leave
  3. Re-join the same lobby

This will have to be tested quite thoroughly. For example, what happens on each service if you try joining a lobby twice? What happens if you join 2 different lobbies?

segfault in OnLobbyDataRetrieveSuccess

void Unet::LobbyListListener::OnLobbyDataRetrieveSuccess(const galaxy::api::GalaxyID& lobbyID)
{
	auto it = std::find(m_listDataFetch.begin(), m_listDataFetch.end(), lobbyID);
	if (it != m_listDataFetch.end()) {
		m_listDataFetch.erase(it);
	}

	xg::Guid unetGuid(galaxy::api::Matchmaking()->GetLobbyData(lobbyID, "unet-guid"));
	if (!unetGuid.isValid()) {
		m_self->m_ctx->GetCallbacks()->OnLogWarn("[Galaxy] unet-guid is not valid!");
		LobbyDataUpdated();
		return;
	}

	ServiceID newEntryPoint;
	newEntryPoint.Service = ServiceType::Galaxy;
	newEntryPoint.ID = lobbyID.ToUint64();
	m_self->m_requestLobbyList->Data->AddEntryPoint(unetGuid, newEntryPoint); // ->Data == nullptr

	LobbyDataUpdated();
}

m_self->m_requestLobbyList->Data->AddEntryPoint(unetGuid, newEntryPoint); // ->Data == nullptr

Code doesn't hit BP on AddServiceRequest where Data is assigned:

		ServiceRequest* AddServiceRequest(Service* service)
		{
			auto newServiceRequest = new ServiceRequest;
			newServiceRequest->Data = &m_result;
			m_requests.emplace_back(newServiceRequest); // Not hit
			return newServiceRequest;
		}

Reproduced with the CLI example (--galaxy .. ..) with side A doing:
create test

side B:
list
list
list (... callback info shows lobbies)
data 0

(or wait also crashes)

Full modified CLI source here: https://github.com/grasmanek94/UniverseLAN/blob/feature/unet-tests/Source/TestCases/Source-unet/TestCase_unet_cli_a.cxx (I don't use GOG galaxy client, I use built-in username/pw login method).

Due the nature of my project I would be interested more in fixing it not only in the unet library (with a new release), but also try to find a way to make this work in the current application without forcing older games to update unet, if possible at all.
I can modify the behaviour of the GOG Galaxy SDK side (my own implementation) to facilitate that, but I'm unsure why in a real/official GOG Galaxy SDK this segfault happens when using the CLI demo.
The target SDK in this case is 1.139.2, didn't test other ones yet.

Edit: Okay so the breakpoint is hit today after starting a debugging session and it works, without making any changes to the CLI code. I think this needs more investigation. Could it be an bug in the GOG Galaxy SDK instead?

XXH static linking is very ugly

We have some modifications in xxhash.cpp which are strange and hacky, but are seemingly necessary for static linkage.

For example:

#define XXH_STATIC_LINKING_ONLY
//TODO: Hack.. UGH
#ifdef WIN32
#undef WIN32
#endif
#include <Unet/xxhash.h>

We should get rid of this ugly code and replace it with the proper way to statically link xxhash code.

std::vector<uint8_t> usage

Please use some form of scratch buffer instead, it's important to have as few allocations and copies of the data we send as we can.

Rename lobby

The host should be able to rename their lobby

Unet::Internal::Context::SendTo implementation

There's some very odd details there,

  1. It has to check the MTU for unreliable messages if its sending one
  2. It shouldnt always send everything as reliable just because its MTU is infinite
  3. Split messages might actually need to be sent as reliable

Add FillLobbyInfo function to Context

Add this function to the context:

// Add all LobbyInfo data via its already-available entry points.
void FillLobbyInfo(LobbyInfo &info);

Probably requires moving the GetLobbyNumPlayers, GetLobbyMaxPlayers, etc functions outside of LobbyListResult and into some other helper class.

OnLobbyMemberInfoChanged

A callback for when information about a lobby has been changed, but it's not lobby member data, so for example a users name

Rich invites

Unet should support rich invites for Steam/Galaxy/Discord, something like this might work:

Unet services subscribe to the appropriate events (for Steam, OnRichPresenceJoinRequested and OnGameLobbyJoinRequested, maybe more).
Whenever such an event is triggered unet requests the lobby.
When the lobby request gets back unet calls its own callback for OnRichJoinLobby(LobbyInfo).

For this to work unet also needs to implement a connect string, this would work something like this:
Whenever a unet service joins or leaves a lobby some function like Unet::RefreshConnectionStrings would be called.
RefreshConnectionStrings would work something like:
First it builds a connection string with the current lobby lobbyinfo, basically append each entry point (steam would have "st99999999", galaxy would have "gx9999999", enet would be "ip255.255.255.255:6555"), so you get a string which holds everything. ("st99999999 gx9999999 ip255.255.255.255:6555")
Send that string to each service, which for Steam and Galaxy just sets the "connect" string.
This is the string we get from the platform callbacks (OnRichPresenceJoinRequested...) and we use to get the lobby.

Add ping

We need to show the ping for each player.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.