Skip to content

Commit

Permalink
Re-add networking-specific code
Browse files Browse the repository at this point in the history
  • Loading branch information
xezno committed Sep 7, 2024
1 parent f0a7ec3 commit 0e2bec8
Show file tree
Hide file tree
Showing 50 changed files with 1,848 additions and 18 deletions.
14 changes: 10 additions & 4 deletions Samples/mocha-minimal/code/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class Game : BaseGame
{
[HotloadSkip] private UIManager Hud { get; set; }

public string NetworkedString { get; set; }
[Sync] public string NetworkedString { get; set; }

public override void OnStartup()
{
Expand All @@ -32,9 +32,15 @@ public override void OnStartup()
}
}

[Event.Tick]
public void Tick()
[Event.Tick, ServerOnly]
public void ServerTick()
{
DebugOverlay.ScreenText( $"Tick... ({GetType().Assembly.GetHashCode()})" );
DebugOverlay.ScreenText( $"Server Tick... ({GetType().Assembly.GetHashCode()})" );
}

[Event.Tick, ClientOnly]
public void ClientTick()
{
DebugOverlay.ScreenText( $"Client Tick... ({GetType().Assembly.GetHashCode()})" );
}
}
18 changes: 18 additions & 0 deletions Source/Mocha.Common/Attributes/Networking/ClientOnlyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Mocha.Common;

[AttributeUsage( AttributeTargets.Class |
AttributeTargets.Interface |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Field |
AttributeTargets.Property |
AttributeTargets.Constructor |
AttributeTargets.Method |
AttributeTargets.Delegate |
AttributeTargets.Event, Inherited = true, AllowMultiple = false )]
public sealed class ClientOnlyAttribute : Attribute
{
public ClientOnlyAttribute()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Mocha;

[System.AttributeUsage( AttributeTargets.Class, Inherited = false, AllowMultiple = false )]
internal sealed class HandlesNetworkedTypeAttribute<T> : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[System.AttributeUsage( AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false )]
public sealed class SyncAttribute : Attribute { }
18 changes: 18 additions & 0 deletions Source/Mocha.Common/Attributes/Networking/ServerOnlyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Mocha.Common;

[AttributeUsage( AttributeTargets.Class |
AttributeTargets.Interface |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Field |
AttributeTargets.Property |
AttributeTargets.Constructor |
AttributeTargets.Method |
AttributeTargets.Delegate |
AttributeTargets.Event, Inherited = true, AllowMultiple = false )]
public sealed class ServerOnlyAttribute : Attribute
{
public ServerOnlyAttribute()
{
}
}
1 change: 1 addition & 0 deletions Source/Mocha.Common/Entities/IEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public interface IEntity
{
string Name { get; set; }
uint NativeHandle { get; }
NetworkId NetworkId { get; set; }

Vector3 Position { get; set; }
Rotation Rotation { get; set; }
Expand Down
11 changes: 11 additions & 0 deletions Source/Mocha.Common/Networking/IClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Mocha.Common;

/// <summary>
/// Represents a client connected to a server.
/// </summary>
public interface IClient
{
public abstract string Name { get; set; }
public abstract int Ping { get; set; }
public abstract IEntity Pawn { get; set; }
}
97 changes: 97 additions & 0 deletions Source/Mocha.Common/Types/NetworkId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
namespace Mocha.Common;

/// <summary>
/// A NetworkId is a wrapper around a 64-bit unsigned integer value used to identify an entity.<br />
/// The first bit of the value is used in order to determine whether the value is networked or local.<br />
/// The binary representation of the value is used to distinguish between local and networked entities.<br />
/// Note that the same ID (e.g., 12345678) can be used twice - once locally, and once networked - to<br />
/// refer to two distinct entities. The IDs used within this class should not reflect the native engine<br />
/// handle for the entity - NetworkIds are unique to a managed context.
/// </summary>
/// <remarks>
/// For example, take an entity with the ID 12345678:<br />
/// <br />
/// <b>Local</b><br />
/// <code>00000000000000000000000000000000000000000000000000000000010111101</code><br />
/// <br />
/// <b>Networked</b><br />
/// <code>10000000000000000000000000000000000000000000000000000000010111101</code><br />
/// <br />
/// Note that the first bit is set to 1 in the binary representation of the networked entity.
/// </remarks>
public class NetworkId : IEquatable<NetworkId>
{
internal ulong Value { get; private set; }

internal NetworkId( ulong value )
{
Value = value;
}

public NetworkId() { }

public bool IsNetworked()
{
// If first bit of the value is set, it's a networked entity
return (Value & 0x8000000000000000) != 0;
}
public bool IsLocal()
{
// If first bit of the value is not set, it's a local entity
return (Value & 0x8000000000000000) == 0;
}

public ulong GetValue()
{
// Returns the value without the first bit
return Value & 0x7FFFFFFFFFFFFFFF;
}

public static NetworkId CreateLocal()
{
// Create a local entity by setting the first bit to 0
// Use EntityRegistry.Instance to get the next available local id
return new( (uint)EntityRegistry.Instance.Count() << 1 );
}

public static NetworkId CreateNetworked()
{
// Create a networked entity by setting the first bit to 1
// Use EntityRegistry.Instance to get the next available local id
return new( (uint)EntityRegistry.Instance.Count() | 0x8000000000000000 );
}

public static implicit operator ulong( NetworkId id ) => id.GetValue();
public static implicit operator NetworkId( ulong value ) => new( value );

public override string ToString()
{
return $"{(IsNetworked() ? "Networked" : "Local")}: {GetValue()} ({Value})";
}

public bool Equals( NetworkId? other )
{
if ( other is null )
return false;

return Value == other.Value;
}

public override bool Equals( object? obj )
{
if ( obj is NetworkId id )
return Equals( id );

return false;
}

public static bool operator ==( NetworkId? a, NetworkId? b )
{
return a?.Equals( b ) ?? false;
}

public static bool operator !=( NetworkId? a, NetworkId? b )
{
return !(a?.Equals( b ) ?? false);
}
}
40 changes: 39 additions & 1 deletion Source/Mocha.Engine/BaseGame.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
namespace Mocha;
using Mocha.Networking;

namespace Mocha;

public class BaseGame : IGame
{
public static BaseGame Current { get; set; }

private static Server? s_server;
private static Client? s_client;

public BaseGame()
{
Current = this;
Expand Down Expand Up @@ -55,6 +60,10 @@ public void FrameUpdate()

public void Update()
{
// TODO: This is garbage and should not be here!!!
s_server?.Update();
s_client?.Update();

if ( Core.IsClient )
{
// HACK: Clear DebugOverlay here because doing it
Expand All @@ -75,6 +84,19 @@ public void Shutdown()

public void Startup()
{
if ( Core.IsClient )
{
s_client = new BaseGameClient( "127.0.0.1" );
}
else
{
s_server = new BaseGameServer()
{
OnClientConnectedEvent = ( connection ) => OnClientConnected( connection.GetClient() ),
OnClientDisconnectedEvent = ( connection ) => OnClientDisconnected( connection.GetClient() ),
};
}

OnStartup();
}

Expand All @@ -93,6 +115,22 @@ public virtual void OnStartup()
public virtual void OnShutdown()
{

}

/// <summary>
/// Called on the server whenever a client joins
/// </summary>
public virtual void OnClientConnected( IClient client )
{

}

/// <summary>
/// Called on the server whenever a client leaves
/// </summary>
public virtual void OnClientDisconnected( IClient client )
{

}
#endregion

Expand Down
124 changes: 124 additions & 0 deletions Source/Mocha.Engine/BaseGameClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using Mocha.Networking;
using System.Reflection;

namespace Mocha;
public class BaseGameClient : Client
{
private ServerConnection _connection;

public BaseGameClient( string ipAddress, ushort port = 10570 ) : base( ipAddress, port )
{
_connection = new ServerConnection();
RegisterHandler<KickedMessage>( OnKickedMessage );
RegisterHandler<SnapshotUpdateMessage>( OnSnapshotUpdateMessage );
RegisterHandler<HandshakeMessage>( OnHandshakeMessage );
}

public override void OnMessageReceived( byte[] data )
{
InvokeHandler( _connection, data );
}

public void OnKickedMessage( IConnection connection, KickedMessage kickedMessage )
{
Log.Info( $"BaseGameClient: We were kicked: '{kickedMessage.Reason}'" );
}

private Type? LocateType( string typeName )
{
var type = Type.GetType( typeName )!;

if ( type != null )
return type;

type = Assembly.GetExecutingAssembly().GetType( typeName );

if ( type != null )
return type;

type = Assembly.GetCallingAssembly().GetType( typeName );

if ( type != null )
return type;

foreach ( var assembly in AppDomain.CurrentDomain.GetAssemblies() )
{
type = assembly.GetType( typeName );
if ( type != null )
return type;
}

return null;
}

public void OnSnapshotUpdateMessage( IConnection connection, SnapshotUpdateMessage snapshotUpdateMessage )
{
foreach ( var entityChange in snapshotUpdateMessage.EntityChanges )
{
// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} changed" );

// Does this entity already exist?
var entity = EntityRegistry.Instance.FirstOrDefault( x => x.NetworkId == entityChange.NetworkId );

if ( entity == null )
{
// Entity doesn't exist locally - let's create it
var type = LocateType( entityChange.TypeName );

if ( type == null )
{
// Log.Error( $"BaseGameClient: Unable to locate type '{entityChange.TypeName}'" );
continue;
}

entity = (Activator.CreateInstance( type ) as IEntity)!;

// Set the network ID
entity.NetworkId = entityChange.NetworkId;

// Log.Info( $"BaseGameClient: Created entity {entity.NetworkId}" );
}

foreach ( var memberChange in entityChange.MemberChanges )
{
if ( memberChange.Data == null )
continue;

var member = entity.GetType().GetMember( memberChange.FieldName ).First()!;
var value = NetworkSerializer.Deserialize( memberChange.Data, member.GetMemberType() );

if ( value == null )
continue;

if ( member.MemberType == MemberTypes.Field )
{
var field = (FieldInfo)member;
field.SetValue( entity, value );

// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} field {memberChange.FieldName} changed to {value}" );
}
else if ( member.MemberType == MemberTypes.Property )
{
var property = (PropertyInfo)member;
property.SetValue( entity, value );

// Log.Info( $"BaseGameClient: Entity {entityChange.NetworkId} property {memberChange.FieldName} changed to {value}" );
}

//if ( memberChange.FieldName == "PhysicsSetup" )
//{
// // Physics setup changed - let's update the physics setup
// var physicsSetup = (ModelEntity.Physics)value;

// if ( physicsSetup.PhysicsModelPath != null )
// ((ModelEntity)entity).SetMeshPhysics( physicsSetup.PhysicsModelPath );
//}
}
}
}

public void OnHandshakeMessage( IConnection connection, HandshakeMessage handshakeMessage )
{
Log.Info( $"BaseGameClient: Handshake received. Tick rate: {handshakeMessage.TickRate}, nickname: {handshakeMessage.Nickname}" );
}
}
Loading

0 comments on commit 0e2bec8

Please sign in to comment.