Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,9 @@ if(WIN32 OR LINUX OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME S
add_definitions(-DHAVE_CPPIPC)
endif()

########################################
add_subdirectory(Externals/GekkoNet)

########################################
# Pre-build events: Define configuration variables and write SCM info header
#

Expand Down
33 changes: 33 additions & 0 deletions Externals/GekkoNet/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
add_library(GekkoNet STATIC
src/backend.cpp
src/event.cpp
src/game_session.cpp
src/gekkonet.cpp
src/input.cpp
src/net.cpp
src/player.cpp
src/spectator_session.cpp
src/storage.cpp
src/stress_session.cpp
src/sync.cpp
)

target_include_directories(GekkoNet
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
)

# Dolphin supplies the UDP transport through its traversed ENet socket. Exclude
# GekkoNet's standalone Asio adapter so Dolphin does not vendor a second stack.
target_compile_definitions(GekkoNet PUBLIC GEKKONET_STATIC GEKKONET_NO_ASIO)
target_compile_features(GekkoNet PUBLIC cxx_std_17)

if(COMMAND dolphin_disable_warnings)
dolphin_disable_warnings(GekkoNet)
endif()

if(MSVC)
target_compile_options(GekkoNet PRIVATE /GR /EHsc /W0)
endif()
24 changes: 24 additions & 0 deletions Externals/GekkoNet/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BSD 2-Clause License

Copyright (c) 2024-2026, Jamie Meyer

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
207 changes: 207 additions & 0 deletions Externals/GekkoNet/include/backend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#pragma once

#include "gekkonet.h"

#include "gekko_types.h"

#include "compression.h"
#include "net.h"
#include "event.h"

#include <memory>
#include <list>
#include <vector>
#include <queue>
#include <chrono>
#include <map>

namespace Gekko {

enum PlayerStatus {
Initiating,
Connected,
Disconnected,
};

struct InputCache {
Frame last_acked_frame = -1;
Frame last_input_frame = -1;
std::vector<InputMsg> packets;

bool IsValid(Frame current_ack, Frame current_last_input) const;
};

struct AdvantageHistory {
public:
void Init();

void Update(Frame frame);

f32 GetAverageAdvantage();

void SetLocalAdvantage(i8 adv);

void SetRemoteAdvantage(i8 adv);

private:
static const i32 HISTORY_SIZE = 26;

i8 _local_frame_adv;

i8 _remote_frame_adv;

i8 _local[HISTORY_SIZE];

i8 _remote[HISTORY_SIZE];
};

class Player
{
public:
Player(Handle phandle, GekkoPlayerType type, NetAddress* addr, u32 magic = 0);

GekkoPlayerType GetType();

PlayerStatus GetStatus();

void SetStatus(PlayerStatus type);

void SetChecksum(Frame frame, u32 checksum);

public:
Handle handle;

u8 sync_num;

u16 session_magic;

NetStats stats;

NetAddress address;

std::map<Frame, u32> session_health;

InputCache input_cache;

u64 last_input_send_time = 0;

AdvantageHistory adv_history;

private:
GekkoPlayerType _type;

PlayerStatus _status;
};

class MessageSystem {
public:
MessageSystem();

void Init(u8 num_players, u32 input_size);

void AddInput(Frame input_frame, Handle player, u8 input[], bool remote = false);

void AddSpectatorInput(Frame input_frame, u8 input[]);

void SendPendingOutput(GekkoNetAdapter* host);

void HandleData(GekkoNetAdapter* host, GekkoNetResult** data, u32 length);

void SendInputAck(Handle player, Frame frame, i8 local_advantage);

Frame GetLastAddedInput(bool spectator = false);

bool CheckStatusActors();

void SendSessionHealth(Frame frame, u32 checksum);

void SendNetworkHealth();

Frame GetLastAddedInputFrom(Handle player);

std::deque<std::unique_ptr<u8[]>>& GetNetPlayerQueue(Handle player);

public:
std::vector<std::unique_ptr<Player>> locals;

std::vector<std::unique_ptr<Player>> remotes;

std::vector<std::unique_ptr<Player>> spectators;

SessionEventSystem session_events;

std::map<Frame, u32> local_health;

struct NetInputQueue {
Frame last_added_input = -1;
std::deque<std::unique_ptr<u8[]>> inputs;

NetInputQueue(const NetInputQueue&) = delete;
NetInputQueue& operator=(const NetInputQueue&) = delete;

NetInputQueue(NetInputQueue&&) = default;
NetInputQueue& operator=(NetInputQueue&&) = default;

NetInputQueue() = default;

void TrimToAck(Frame min_ack, u32 max_size);
};

private:
void SendSyncRequest(NetAddress* addr);

void SendSyncResponse(NetAddress* addr, u16 magic);

void SendInputsToPeer(Player* peer, GekkoNetAdapter* host, bool spectator);

std::vector<Handle> GetRemoteHandlesForAddress(NetAddress* addr);

Player* GetPlayerByHandle(Handle handle);

Frame GetMinLastAckedFrame(bool spectator = false);

void HandleTooFarBehindActors(bool spectator = false);

u64 TimeSinceEpoch();

void SendDataToAll(NetData* pkt, GekkoNetAdapter* host, bool spectators_only = false);

void SendDataTo(NetData* pkt, GekkoNetAdapter* host);

void ParsePacket(NetAddress& addr, NetPacket& pkt, u32 packet_size);

void OnSyncRequest(NetAddress& addr, NetPacket& pkt);

void OnSyncResponse(NetAddress& addr, NetPacket& pkt);

void OnInputs(NetAddress& addr, NetPacket& pkt);

void OnInputAck(NetAddress& addr, NetPacket& pkt);

void OnSessionHealth(NetAddress& addr, NetPacket& pkt);

void OnNetworkHealth(NetAddress& addr, NetPacket& pkt);

private:
const u32 MAX_INPUT_QUEUE_SIZE = 128;
const u32 NUM_TO_SYNC = 4;

u32 _input_size;

u16 _session_magic;

u8 _num_players;

// input queue for each player for either sending or receiving
std::vector<NetInputQueue> _net_player_queue;

// input queue for spectator inputs
NetInputQueue _net_spectator_queue;

std::queue<std::unique_ptr<NetData>> _pending_output;

std::vector<u8> _bin_buffer;

u64 _last_sent_network_check;
};
}
61 changes: 61 additions & 0 deletions Externals/GekkoNet/include/compression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include "gekko_types.h"

#include <iostream>
#include <vector>

namespace Gekko {
struct Compression {

static void PrintArray(const uint8_t* data, u32 length) {
for (u32 i = 0; i < length; i++) {
std::cout << +data[i] << ' ';
}
std::cout << std::endl;
std::cout << "_______________\n";
}

static std::vector<uint8_t> RLEEncode(const uint8_t* data, u32 length) {
std::vector<uint8_t> result;

u32 idx = 0;
u8 count = 0;

while (idx < length) {
count = 1;
while (count != UINT8_MAX && idx + 1 < length && data[idx] == data[idx + 1]) {
idx++;
count++;
}
result.push_back(count);
result.push_back(data[idx]);
idx++;
}
return result;
}

static std::vector<uint8_t> RLEDecode(const uint8_t* data, u32 length) {
std::vector<uint8_t> result;

u32 idx = 0;
u8 count = 0;
u8 value = 0;

while (idx < length) {
count = data[idx];
value = data[idx + 1];

for (i32 x = 0; x < count; x++) {
result.push_back(value);
}

idx += 2;
}

return result;
}

Compression() = delete;
};
}
Loading