Skip to content
This repository was archived by the owner on Sep 15, 2024. It is now read-only.

Commit 44a36a1

Browse files
Add start of netcode
1 parent 26c7fbd commit 44a36a1

33 files changed

+1147
-0
lines changed

ENet-CSharp.dll

19.5 KB
Binary file not shown.

LuaModdingTest.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
<PackageReference Include="MoonSharp" Version="2.0.0" />
77
<PackageReference Include="MoonSharp.Debugger.VsCode" Version="2.0.0" />
88
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <!--This is used because net472 does not have System.Text.Json-->
9+
<Reference Include="ENet-CSharp">
10+
<HintPath>ENet-CSharp.dll</HintPath>
11+
</Reference>
912
</ItemGroup>
1013
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
using Godot;
2+
using System;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using System.Collections.Generic;
7+
using System.Collections.Concurrent;
8+
using ENet;
9+
using Common.Netcode;
10+
11+
using Thread = System.Threading.Thread; // CS0104: Ambigious reference between 'Godot.Thread' and 'System.Threading.Thread'
12+
using Version = Common.Netcode.Version; // CS0104: Ambiguous reference between 'Common.Netcode.Version' and 'System.Version'
13+
14+
namespace Valk.Modules.Netcode.Client
15+
{
16+
public abstract class ENetClient : Node
17+
{
18+
public static readonly ConcurrentQueue<ClientPacket> Outgoing = new ConcurrentQueue<ClientPacket>();
19+
public static readonly ConcurrentQueue<ENetCmd> ENetCmds = new ConcurrentQueue<ENetCmd>();
20+
21+
private static readonly ConcurrentBag<Packet> Incoming = new ConcurrentBag<Packet>();
22+
private static readonly ConcurrentQueue<GodotCmd> GodotCmds = new ConcurrentQueue<GodotCmd>();
23+
private static readonly Dictionary<ServerPacketOpcode, HandlePacket> HandlePacket = typeof(HandlePacket).Assembly.GetTypes().Where(x => typeof(HandlePacket).IsAssignableFrom(x) && !x.IsAbstract).Select(Activator.CreateInstance).Cast<HandlePacket>().ToDictionary(x => x.Opcode, x => x);
24+
public static bool ENetThreadRunning;
25+
private static bool RunningNetCode;
26+
27+
public static readonly Version Version = new Version { Major = 0, Minor = 1, Patch = 0 };
28+
29+
public abstract void ProcessGodotCommands(GodotCmd cmd);
30+
public abstract void Connect(Event netEvent);
31+
public abstract void Disconnect(Event netEvent);
32+
public abstract void Timeout(Event netEvent);
33+
34+
public override void _Process(float delta)
35+
{
36+
while (GodotCmds.TryDequeue(out GodotCmd cmd))
37+
{
38+
switch (cmd.Opcode)
39+
{
40+
case GodotOpcode.ENetPacket:
41+
var packetReader = (PacketReader)cmd.Data[0];
42+
var opcode = (ServerPacketOpcode)packetReader.ReadByte();
43+
44+
HandlePacket[opcode].Handle(packetReader);
45+
packetReader.Dispose();
46+
return;
47+
case GodotOpcode.LogMessage:
48+
GD.Print((string)cmd.Data[0]);
49+
return;
50+
case GodotOpcode.ExitApp:
51+
GetTree().Quit();
52+
return;
53+
}
54+
55+
ProcessGodotCommands(cmd);
56+
}
57+
}
58+
59+
public void Connect(string ip, ushort port, string jwt)
60+
{
61+
if (ENetThreadRunning)
62+
{
63+
GD.Print("ENet thread is running already");
64+
return;
65+
}
66+
67+
ENetThreadRunning = true;
68+
Task.Run(() => ENetThreadWorker(ip, port, jwt));
69+
}
70+
71+
private void ENetThreadWorker(string ip, ushort port, string jwt)
72+
{
73+
Library.Initialize();
74+
GDLog("ENet library initialized successfully");
75+
var wantsToExit = false;
76+
var wantsToDisconnect = false;
77+
78+
using (var client = new Host())
79+
{
80+
var address = new Address();
81+
82+
address.SetHost(ip);
83+
address.Port = port;
84+
client.Create();
85+
86+
//GDLog("Attempting to connect to the game server...");
87+
var peer = client.Connect(address);
88+
89+
uint pingInterval = 1000; // Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes.
90+
uint timeout = 5000; // Will be ignored if maximum timeout is exceeded
91+
uint timeoutMinimum = 5000; // The timeout for server not sending the packet to the client sent from the server
92+
uint timeoutMaximum = 5000; // The timeout for server not receiving the packet sent from the client
93+
94+
peer.PingInterval(pingInterval);
95+
peer.Timeout(timeout, timeoutMinimum, timeoutMaximum);
96+
97+
RunningNetCode = true;
98+
while (RunningNetCode) {
99+
var polled = false;
100+
101+
// ENet Cmds from Godot Thread
102+
while (ENetCmds.TryDequeue(out ENetCmd cmd))
103+
{
104+
switch (cmd.Opcode)
105+
{
106+
case ENetOpcode.ClientWantsToExitApp:
107+
peer.Disconnect(0);
108+
RunningNetCode = false;
109+
wantsToExit = true;
110+
break;
111+
case ENetOpcode.ClientWantsToDisconnect:
112+
peer.Disconnect(0);
113+
RunningNetCode = false;
114+
wantsToDisconnect = true;
115+
break;
116+
}
117+
}
118+
119+
// Incoming
120+
while (Incoming.TryTake(out Packet packet))
121+
GodotCmds.Enqueue(new GodotCmd { Opcode = GodotOpcode.ENetPacket, Data = new List<object> { new PacketReader(packet) }});
122+
123+
// Outgoing
124+
while (Outgoing.TryDequeue(out ClientPacket clientPacket))
125+
{
126+
byte channelID = 0; // The channel all networking traffic will be going through
127+
var packet = default(Packet);
128+
packet.Create(clientPacket.Data, clientPacket.PacketFlags);
129+
peer.Send(channelID, ref packet);
130+
}
131+
132+
while (!polled) {
133+
if (client.CheckEvents(out Event netEvent) <= 0) {
134+
if (client.Service(15, out netEvent) <= 0)
135+
break;
136+
137+
polled = true;
138+
}
139+
140+
switch (netEvent.Type)
141+
{
142+
case EventType.Connect:
143+
// Send login request
144+
Outgoing.Enqueue(new ClientPacket((byte)ClientPacketOpcode.Login, new WPacketLogin
145+
{
146+
JsonWebToken = jwt,
147+
VersionMajor = Version.Major,
148+
VersionMinor = Version.Minor,
149+
VersionPatch = Version.Patch
150+
}));
151+
152+
Connect(netEvent);
153+
break;
154+
case EventType.Receive:
155+
// Receive
156+
var packet = netEvent.Packet;
157+
if (packet.Length > GamePacket.MaxSize)
158+
{
159+
GDLog($"Tried to read packet from server of size {packet.Length} when max packet size is {GamePacket.MaxSize}");
160+
packet.Dispose();
161+
continue;
162+
}
163+
164+
Incoming.Add(netEvent.Packet);
165+
break;
166+
case EventType.Timeout:
167+
RunningNetCode = false;
168+
wantsToDisconnect = true;
169+
Timeout(netEvent);
170+
break;
171+
case EventType.Disconnect:
172+
RunningNetCode = false;
173+
wantsToDisconnect = true;
174+
Disconnect(netEvent);
175+
break;
176+
}
177+
}
178+
}
179+
180+
client.Flush();
181+
}
182+
183+
Library.Deinitialize();
184+
ENetThreadRunning = false;
185+
186+
if (wantsToDisconnect)
187+
GodotCmds.Enqueue(new GodotCmd { Opcode = GodotOpcode.LoadMainMenu });
188+
189+
if (wantsToExit)
190+
GodotCmds.Enqueue(new GodotCmd { Opcode = GodotOpcode.ExitApp });
191+
}
192+
193+
protected static void GDLog(string text) => GodotCmds.Enqueue(new GodotCmd { Opcode = GodotOpcode.LogMessage, Data = new List<object> { text }});
194+
}
195+
196+
public class GodotCmd
197+
{
198+
public GodotOpcode Opcode { get; set; }
199+
public List<object> Data { get; set; }
200+
}
201+
202+
public class ENetCmd
203+
{
204+
public ENetOpcode Opcode { get; set; }
205+
public List<object> Data { get; set; }
206+
}
207+
208+
public enum GodotOpcode
209+
{
210+
ENetPacket,
211+
LogMessage,
212+
LoadMainMenu,
213+
ExitApp
214+
}
215+
216+
public enum ENetOpcode
217+
{
218+
ClientWantsToExitApp,
219+
ClientWantsToDisconnect
220+
}
221+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using ENet;
2+
3+
namespace Valk.Modules.Netcode.Client
4+
{
5+
public class GameClient : ENetClient
6+
{
7+
public static string Username = "Unnamed";
8+
9+
public override void ProcessGodotCommands(GodotCmd cmd)
10+
{
11+
switch (cmd.Opcode)
12+
{
13+
case GodotOpcode.LoadMainMenu:
14+
//UIGameMenu.ClientPressedDisconnect = false;
15+
//UIMainMenu.LoadMainMenu();
16+
break;
17+
}
18+
}
19+
20+
public override void Connect(Event netEvent)
21+
{
22+
GDLog("Client connected to server");
23+
}
24+
25+
public override void Timeout(Event netEvent)
26+
{
27+
GDLog("Client connection timeout");
28+
}
29+
30+
public override void Disconnect(Event netEvent)
31+
{
32+
GDLog("Client disconnected from server");
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Common.Netcode;
2+
using ENet;
3+
using System.Linq;
4+
using Godot;
5+
6+
namespace Valk.Modules.Netcode.Client
7+
{
8+
public class HandlePacketLogin : HandlePacket
9+
{
10+
public override ServerPacketOpcode Opcode { get; set; }
11+
12+
public HandlePacketLogin() => Opcode = ServerPacketOpcode.LoginResponse;
13+
14+
public override void Handle(PacketReader packetReader)
15+
{
16+
var data = new RPacketLogin();
17+
data.Read(packetReader);
18+
19+
var opcode = data.LoginOpcode;
20+
21+
if (opcode == LoginResponseOpcode.InvalidToken)
22+
{
23+
//UILogin.UpdateResponse("Invalid token");
24+
return;
25+
}
26+
27+
if (opcode == LoginResponseOpcode.VersionMismatch)
28+
{
29+
//var serverVersion = $"{data.Version.Major}.{data.Version.Minor}.{data.Version.Patch}";
30+
//var clientVersion = $"{GameClient.Version.Major}.{GameClient.Version.Minor}.{GameClient.Version.Patch}";
31+
//var message = $"Version mismatch. Server ver. {serverVersion} Client ver. {clientVersion}";
32+
33+
//UILogin.UpdateResponse("Version mismatch");
34+
return;
35+
}
36+
37+
//UILogin.UpdateResponse("Logged in to game server");
38+
//UILogin.LoadGameScene();
39+
}
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Common.Netcode;
2+
using ENet;
3+
using Godot;
4+
5+
namespace Valk.Modules.Netcode.Client
6+
{
7+
public abstract class HandlePacket
8+
{
9+
public abstract ServerPacketOpcode Opcode { get; set; }
10+
11+
public abstract void Handle(PacketReader packetReader);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Common.Netcode;
2+
using Godot;
3+
using System.Collections.Generic;
4+
using System.Drawing;
5+
using System.IO;
6+
using Common.Game;
7+
8+
namespace Valk.Modules.Netcode.Client
9+
{
10+
public class RPacketLogin : IReadable
11+
{
12+
public LoginResponseOpcode LoginOpcode { get; set; }
13+
public Version Version { get; set; }
14+
public Dictionary<uint, Player> Players = new Dictionary<uint, Player>();
15+
public Dictionary<uint, Channel> Channels = new Dictionary<uint, Channel>();
16+
17+
public void Read(PacketReader reader)
18+
{
19+
LoginOpcode = (LoginResponseOpcode)reader.ReadByte();
20+
21+
if (LoginOpcode == LoginResponseOpcode.VersionMismatch)
22+
{
23+
Version = new Version {
24+
Major = reader.ReadByte(),
25+
Minor = reader.ReadByte(),
26+
Patch = reader.ReadByte()
27+
};
28+
return;
29+
}
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Common.Netcode;
2+
3+
namespace Valk.Modules.Netcode.Client
4+
{
5+
public class WPacketChatMessage : IWritable
6+
{
7+
public uint ChannelId { get; set; }
8+
public string Message { get; set; }
9+
10+
public void Write(PacketWriter writer)
11+
{
12+
writer.Write(ChannelId);
13+
writer.Write(Message);
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)