Skip to content

Commit 527cc69

Browse files
committed
Re-add networking-specific code
1 parent 897f173 commit 527cc69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1848
-18
lines changed

Samples/mocha-minimal/code/Game.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public class Game : BaseGame
77
{
88
[HotloadSkip] private UIManager Hud { get; set; }
99

10-
public string NetworkedString { get; set; }
10+
[Sync] public string NetworkedString { get; set; }
1111

1212
public override void OnStartup()
1313
{
@@ -32,9 +32,15 @@ public override void OnStartup()
3232
}
3333
}
3434

35-
[Event.Tick]
36-
public void Tick()
35+
[Event.Tick, ServerOnly]
36+
public void ServerTick()
3737
{
38-
DebugOverlay.ScreenText( $"Tick... ({GetType().Assembly.GetHashCode()})" );
38+
DebugOverlay.ScreenText( $"Server Tick... ({GetType().Assembly.GetHashCode()})" );
39+
}
40+
41+
[Event.Tick, ClientOnly]
42+
public void ClientTick()
43+
{
44+
DebugOverlay.ScreenText( $"Client Tick... ({GetType().Assembly.GetHashCode()})" );
3945
}
4046
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Mocha.Common;
2+
3+
[AttributeUsage( AttributeTargets.Class |
4+
AttributeTargets.Interface |
5+
AttributeTargets.Struct |
6+
AttributeTargets.Enum |
7+
AttributeTargets.Field |
8+
AttributeTargets.Property |
9+
AttributeTargets.Constructor |
10+
AttributeTargets.Method |
11+
AttributeTargets.Delegate |
12+
AttributeTargets.Event, Inherited = true, AllowMultiple = false )]
13+
public sealed class ClientOnlyAttribute : Attribute
14+
{
15+
public ClientOnlyAttribute()
16+
{
17+
}
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Mocha;
2+
3+
[System.AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false )]
4+
internal sealed class HandlesNetworkedTypeAttribute<T> : Attribute
5+
{
6+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[System.AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false )]
2+
public sealed class SyncAttribute : Attribute { }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Mocha.Common;
2+
3+
[AttributeUsage( AttributeTargets.Class |
4+
AttributeTargets.Interface |
5+
AttributeTargets.Struct |
6+
AttributeTargets.Enum |
7+
AttributeTargets.Field |
8+
AttributeTargets.Property |
9+
AttributeTargets.Constructor |
10+
AttributeTargets.Method |
11+
AttributeTargets.Delegate |
12+
AttributeTargets.Event, Inherited = true, AllowMultiple = false )]
13+
public sealed class ServerOnlyAttribute : Attribute
14+
{
15+
public ServerOnlyAttribute()
16+
{
17+
}
18+
}

Source/Mocha.Common/Entities/IEntity.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public interface IEntity
44
{
55
string Name { get; set; }
66
uint NativeHandle { get; }
7+
NetworkId NetworkId { get; set; }
78

89
Vector3 Position { get; set; }
910
Rotation Rotation { get; set; }
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Mocha.Common;
2+
3+
/// <summary>
4+
/// Represents a client connected to a server.
5+
/// </summary>
6+
public interface IClient
7+
{
8+
public abstract string Name { get; set; }
9+
public abstract int Ping { get; set; }
10+
public abstract IEntity Pawn { get; set; }
11+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
namespace Mocha.Common;
2+
3+
/// <summary>
4+
/// A NetworkId is a wrapper around a 64-bit unsigned integer value used to identify an entity.<br />
5+
/// The first bit of the value is used in order to determine whether the value is networked or local.<br />
6+
/// The binary representation of the value is used to distinguish between local and networked entities.<br />
7+
/// Note that the same ID (e.g., 12345678) can be used twice - once locally, and once networked - to<br />
8+
/// refer to two distinct entities. The IDs used within this class should not reflect the native engine<br />
9+
/// handle for the entity - NetworkIds are unique to a managed context.
10+
/// </summary>
11+
/// <remarks>
12+
/// For example, take an entity with the ID 12345678:<br />
13+
/// <br />
14+
/// <b>Local</b><br />
15+
/// <code>00000000000000000000000000000000000000000000000000000000010111101</code><br />
16+
/// <br />
17+
/// <b>Networked</b><br />
18+
/// <code>10000000000000000000000000000000000000000000000000000000010111101</code><br />
19+
/// <br />
20+
/// Note that the first bit is set to 1 in the binary representation of the networked entity.
21+
/// </remarks>
22+
public class NetworkId : IEquatable<NetworkId>
23+
{
24+
internal ulong Value { get; private set; }
25+
26+
internal NetworkId( ulong value )
27+
{
28+
Value = value;
29+
}
30+
31+
public NetworkId() { }
32+
33+
public bool IsNetworked()
34+
{
35+
// If first bit of the value is set, it's a networked entity
36+
return (Value & 0x8000000000000000) != 0;
37+
}
38+
public bool IsLocal()
39+
{
40+
// If first bit of the value is not set, it's a local entity
41+
return (Value & 0x8000000000000000) == 0;
42+
}
43+
44+
public ulong GetValue()
45+
{
46+
// Returns the value without the first bit
47+
return Value & 0x7FFFFFFFFFFFFFFF;
48+
}
49+
50+
public static NetworkId CreateLocal()
51+
{
52+
// Create a local entity by setting the first bit to 0
53+
// Use EntityRegistry.Instance to get the next available local id
54+
return new( (uint)EntityRegistry.Instance.Count() << 1 );
55+
}
56+
57+
public static NetworkId CreateNetworked()
58+
{
59+
// Create a networked entity by setting the first bit to 1
60+
// Use EntityRegistry.Instance to get the next available local id
61+
return new( (uint)EntityRegistry.Instance.Count() | 0x8000000000000000 );
62+
}
63+
64+
public static implicit operator ulong( NetworkId id ) => id.GetValue();
65+
public static implicit operator NetworkId( ulong value ) => new( value );
66+
67+
public override string ToString()
68+
{
69+
return $"{(IsNetworked() ? "Networked" : "Local")}: {GetValue()} ({Value})";
70+
}
71+
72+
public bool Equals( NetworkId? other )
73+
{
74+
if ( other is null )
75+
return false;
76+
77+
return Value == other.Value;
78+
}
79+
80+
public override bool Equals( object? obj )
81+
{
82+
if ( obj is NetworkId id )
83+
return Equals( id );
84+
85+
return false;
86+
}
87+
88+
public static bool operator ==( NetworkId? a, NetworkId? b )
89+
{
90+
return a?.Equals( b ) ?? false;
91+
}
92+
93+
public static bool operator !=( NetworkId? a, NetworkId? b )
94+
{
95+
return !(a?.Equals( b ) ?? false);
96+
}
97+
}

Source/Mocha.Engine/BaseGame.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
namespace Mocha;
1+
using Mocha.Networking;
2+
3+
namespace Mocha;
24

35
public class BaseGame : IGame
46
{
57
public static BaseGame Current { get; set; }
68

9+
private static Server? s_server;
10+
private static Client? s_client;
11+
712
public BaseGame()
813
{
914
Current = this;
@@ -55,6 +60,10 @@ public void FrameUpdate()
5560

5661
public void Update()
5762
{
63+
// TODO: This is garbage and should not be here!!!
64+
s_server?.Update();
65+
s_client?.Update();
66+
5867
if ( Core.IsClient )
5968
{
6069
// HACK: Clear DebugOverlay here because doing it
@@ -75,6 +84,19 @@ public void Shutdown()
7584

7685
public void Startup()
7786
{
87+
if ( Core.IsClient )
88+
{
89+
s_client = new BaseGameClient( "127.0.0.1" );
90+
}
91+
else
92+
{
93+
s_server = new BaseGameServer()
94+
{
95+
OnClientConnectedEvent = ( connection ) => OnClientConnected( connection.GetClient() ),
96+
OnClientDisconnectedEvent = ( connection ) => OnClientDisconnected( connection.GetClient() ),
97+
};
98+
}
99+
78100
OnStartup();
79101
}
80102

@@ -93,6 +115,22 @@ public virtual void OnStartup()
93115
public virtual void OnShutdown()
94116
{
95117

118+
}
119+
120+
/// <summary>
121+
/// Called on the server whenever a client joins
122+
/// </summary>
123+
public virtual void OnClientConnected( IClient client )
124+
{
125+
126+
}
127+
128+
/// <summary>
129+
/// Called on the server whenever a client leaves
130+
/// </summary>
131+
public virtual void OnClientDisconnected( IClient client )
132+
{
133+
96134
}
97135
#endregion
98136

Source/Mocha.Engine/BaseGameClient.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using Mocha.Networking;
2+
using System.Reflection;
3+
4+
namespace Mocha;
5+
public class BaseGameClient : Client
6+
{
7+
private ServerConnection _connection;
8+
9+
public BaseGameClient( string ipAddress, ushort port = 10570 ) : base( ipAddress, port )
10+
{
11+
_connection = new ServerConnection();
12+
RegisterHandler<KickedMessage>( OnKickedMessage );
13+
RegisterHandler<SnapshotUpdateMessage>( OnSnapshotUpdateMessage );
14+
RegisterHandler<HandshakeMessage>( OnHandshakeMessage );
15+
}
16+
17+
public override void OnMessageReceived( byte[] data )
18+
{
19+
InvokeHandler( _connection, data );
20+
}
21+
22+
public void OnKickedMessage( IConnection connection, KickedMessage kickedMessage )
23+
{
24+
Log.Info( $"BaseGameClient: We were kicked: '{kickedMessage.Reason}'" );
25+
}
26+
27+
private Type? LocateType( string typeName )
28+
{
29+
var type = Type.GetType( typeName )!;
30+
31+
if ( type != null )
32+
return type;
33+
34+
type = Assembly.GetExecutingAssembly().GetType( typeName );
35+
36+
if ( type != null )
37+
return type;
38+
39+
type = Assembly.GetCallingAssembly().GetType( typeName );
40+
41+
if ( type != null )
42+
return type;
43+
44+
foreach ( var assembly in AppDomain.CurrentDomain.GetAssemblies() )
45+
{
46+
type = assembly.GetType( typeName );
47+
if ( type != null )
48+
return type;
49+
}
50+
51+
return null;
52+
}
53+
54+
public void OnSnapshotUpdateMessage( IConnection connection, SnapshotUpdateMessage snapshotUpdateMessage )
55+
{
56+
foreach ( var entityChange in snapshotUpdateMessage.EntityChanges )
57+
{
58+
// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} changed" );
59+
60+
// Does this entity already exist?
61+
var entity = EntityRegistry.Instance.FirstOrDefault( x => x.NetworkId == entityChange.NetworkId );
62+
63+
if ( entity == null )
64+
{
65+
// Entity doesn't exist locally - let's create it
66+
var type = LocateType( entityChange.TypeName );
67+
68+
if ( type == null )
69+
{
70+
// Log.Error( $"BaseGameClient: Unable to locate type '{entityChange.TypeName}'" );
71+
continue;
72+
}
73+
74+
entity = (Activator.CreateInstance( type ) as IEntity)!;
75+
76+
// Set the network ID
77+
entity.NetworkId = entityChange.NetworkId;
78+
79+
// Log.Info( $"BaseGameClient: Created entity {entity.NetworkId}" );
80+
}
81+
82+
foreach ( var memberChange in entityChange.MemberChanges )
83+
{
84+
if ( memberChange.Data == null )
85+
continue;
86+
87+
var member = entity.GetType().GetMember( memberChange.FieldName ).First()!;
88+
var value = NetworkSerializer.Deserialize( memberChange.Data, member.GetMemberType() );
89+
90+
if ( value == null )
91+
continue;
92+
93+
if ( member.MemberType == MemberTypes.Field )
94+
{
95+
var field = (FieldInfo)member;
96+
field.SetValue( entity, value );
97+
98+
// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} field {memberChange.FieldName} changed to {value}" );
99+
}
100+
else if ( member.MemberType == MemberTypes.Property )
101+
{
102+
var property = (PropertyInfo)member;
103+
property.SetValue( entity, value );
104+
105+
// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} property {memberChange.FieldName} changed to {value}" );
106+
}
107+
108+
//if ( memberChange.FieldName == "PhysicsSetup" )
109+
//{
110+
// // Physics setup changed - let's update the physics setup
111+
// var physicsSetup = (ModelEntity.Physics)value;
112+
113+
// if ( physicsSetup.PhysicsModelPath != null )
114+
// ((ModelEntity)entity).SetMeshPhysics( physicsSetup.PhysicsModelPath );
115+
//}
116+
}
117+
}
118+
}
119+
120+
public void OnHandshakeMessage( IConnection connection, HandshakeMessage handshakeMessage )
121+
{
122+
Log.Info( $"BaseGameClient: Handshake received. Tick rate: {handshakeMessage.TickRate}, nickname: {handshakeMessage.Nickname}" );
123+
}
124+
}

0 commit comments

Comments
 (0)