using Sandbox.Network; using Sandbox.Utility; using System.IO; using System.Text.Json.Nodes; namespace Sandbox; /// /// This is created and referenced by the network system, as a way to route. /// [Expose] public partial class SceneNetworkSystem : GameNetworkSystem { internal static SceneNetworkSystem Instance { get; set; } internal DeltaSnapshotSystem DeltaSnapshots { get; private set; } private List BatchSpawnList { get; set; } = []; private static bool IsSupressingSpawnMessages { get; set; } private bool IsBatchNetworkSpawning { get; set; } private int BatchNetworkSpawnCount { get; set; } internal override bool IsHostBusy => !Game.ActiveScene?.IsLoading ?? true; internal bool IsDisconnecting { get; set; } internal SceneNetworkSystem( Internal.TypeLibrary typeLibrary, NetworkSystem system ) { Instance = this; DeltaSnapshots = new( this ); Library = typeLibrary; NetworkSystem = system; AddHandler( OnObjectCreateBatch ); AddHandler( OnObjectCreate ); AddHandler( OnObjectRefresh ); AddHandler( OnObjectDestroyComponent ); AddHandler( OnObjectDestroyDescendant ); AddHandler( OnObjectRefreshComponent ); AddHandler( OnObjectRefreshDescendant ); AddHandler( OnObjectRefreshAck ); AddHandler( OnObjectDestroy ); AddHandler( OnObjectMessage ); AddHandler( OnNetworkTableChanges ); AddHandler( OnNetworkTableChanges ); AddHandler( OnSceneRpc ); AddHandler( OnStaticRpc ); AddHandler( OnLoadSceneMsg ); AddHandler( OnLoadSceneSnapshotMsg ); AddHandler( OnLoadSceneRequestSnapshotMsg ); AddHandler( OnSceneLoadedMsg ); } internal void OnHotload() { DeltaSnapshots.Reset(); } /// /// Any GameObjects created within this scope will not send spawn messages to other clients. /// internal static IDisposable SuppressSpawnMessages() { IsSupressingSpawnMessages = true; return new DisposeAction( () => { IsSupressingSpawnMessages = false; } ); } private readonly Dictionary PendingSceneLoads = new(); /// /// Load a scene for all other clients. This can only be called by the host. /// internal void LoadSceneBroadcast( SceneLoadOptions options ) { if ( !Networking.IsActive || !Networking.IsHost ) return; var loadMsg = new LoadSceneBeginMsg { ShowLoadingScreen = options.ShowLoadingScreen, MountedVPKs = Game.ActiveScene.GetAllComponents().Select( x => x.MapName ).ToList(), Id = Guid.NewGuid() }; var msg = ByteStream.Create( 256 ); msg.Write( InternalMessageType.Packed ); Networking.System.Serialize( loadMsg, ref msg ); foreach ( var c in Connection.All ) { if ( c.State < Connection.ChannelState.Snapshot ) continue; PendingSceneLoads[c.Id] = loadMsg.Id; c.SendRawMessage( msg ); } msg.Dispose(); } /// /// Start a network spawn batch. Any networked objects created within this scope /// will be sent with one spawn message. This makes sure that any references are /// kept to child networked objects when the objects are spawned on the other side. /// /// internal IDisposable NetworkSpawnBatch() { IsBatchNetworkSpawning = true; BatchNetworkSpawnCount++; return new DisposeAction( () => { BatchNetworkSpawnCount--; if ( BatchNetworkSpawnCount > 0 ) return; IsBatchNetworkSpawning = false; SendNetworkSpawnBatch(); } ); } private void SendNetworkSpawnBatch() { // If we only have one, just send a normal message. if ( BatchSpawnList.Count == 1 ) { var networkObject = BatchSpawnList.FirstOrDefault(); if ( !(networkObject.GameObject?.IsDestroyed ?? true) ) Broadcast( networkObject.GetCreateMessage() ); BatchSpawnList.Clear(); return; } var msg = new ObjectCreateBatchMsg(); var list = new List(); foreach ( var networkObject in BatchSpawnList ) { if ( networkObject.GameObject?.IsDestroyed ?? true ) continue; list.Add( networkObject.GetCreateMessage() ); } msg.CreateMsgs = list.ToArray(); BatchSpawnList.Clear(); Broadcast( msg ); } /// /// Broadcast the spawning of a networked object. This will add the networked object /// to batch list if we're spawning as part of a batch, and will ignore the spawn message /// entirely if we're supposed to be suppressing spawn messages. /// /// internal void NetworkSpawnBroadcast( NetworkObject networkObject ) { // We're not supposed to send spawn messages right now. if ( IsSupressingSpawnMessages ) return; if ( IsBatchNetworkSpawning ) { BatchSpawnList.Add( networkObject ); return; } Broadcast( networkObject.GetCreateMessage() ); } /// /// Called when the host has provided us with a snapshot for a newly loaded scene. /// private async Task OnLoadSceneSnapshotMsg( LoadSceneSnapshotMsg msg, Connection connection, Guid msgId ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Snapshot, msg ); await SetSnapshotAsync( msg.Snapshot ); // Let them know we have now loaded this scene. var loadedMsg = new SceneLoadedMsg { Id = msg.Id }; connection.SendMessage( loadedMsg, NetFlags.Reliable ); LoadingScreen.IsVisible = false; } /// /// Called when a client has requested the snapshot for a newly loaded scene. This is usually /// once they've done any preloading that they need to do. /// private void OnLoadSceneRequestSnapshotMsg( LoadSceneRequestSnapshotMsg msg, Connection connection, Guid msgId ) { // If this connection doesn't have a pending scene load with this id then bail. if ( !PendingSceneLoads.TryGetValue( connection.Id, out var id ) || id != msg.Id ) return; var output = new LoadSceneSnapshotMsg { Id = msg.Id }; var snapshot = new SnapshotMsg { GameObjectSystems = [], NetworkObjects = new( 64 ) }; GetSnapshot( default, ref snapshot ); output.Snapshot = snapshot; var bs = ByteStream.Create( 256 ); bs.Write( InternalMessageType.Packed ); Networking.System.Serialize( output, ref bs ); connection.SendRawMessage( bs ); bs.Dispose(); } /// /// Called when the host has told us to load a new scene. /// private async Task OnLoadSceneMsg( LoadSceneBeginMsg msg, Connection connection, Guid msgId ) { if ( !Game.IsEditor && msg.ShowLoadingScreen ) { LoadingScreen.IsVisible = true; LoadingScreen.Title = "Loading Scene"; } // Go ahead and destroy the scene if ( Game.ActiveScene is not null ) { Game.ActiveScene?.Destroy(); Game.ActiveScene = null; } MountedVPKs?.Dispose(); MountedVPKs = await MountMaps( msg.MountedVPKs ); // Let them know we would like a snapshot now. var loadedMsg = new LoadSceneRequestSnapshotMsg { Id = msg.Id }; connection.SendMessage( loadedMsg, NetFlags.Reliable ); } /// /// Called by clients to confirm they have finished loading the new scene. /// private void OnSceneLoadedMsg( SceneLoadedMsg msg, Connection connection, Guid msgId ) { // If this connection doesn't have a pending scene load with this id then bail. if ( !PendingSceneLoads.TryGetValue( connection.Id, out var id ) || id != msg.Id ) return; PendingSceneLoads.Remove( connection.Id ); Instance?.OnJoined( connection ); } /// /// A client has joined and wants to know what VPKs to preload. /// public override void GetMountedVPKs( Connection source, ref MountedVPKsResponse msg ) { msg.MountedVPKs = Game.ActiveScene.GetAllComponents().Select( x => x.MapName ).ToList(); } /// /// Asynchronously load and mount any VPKs from the provided server response. /// public override async Task MountVPKs( Connection source, MountedVPKsResponse msg ) { // Mount any vpks early because snapshotted or networked objects can use resources within // This removes it's refcount at the end because the MapInstance should take over MountedVPKs?.Dispose(); MountedVPKs = await MountMaps( msg.MountedVPKs ); } private static readonly GameObject.SerializeOptions _snapshotSerializeOptions = new() { SceneForNetwork = true }; /// /// A client has joined and wants a snapshot of the world. /// public override void GetSnapshot( Connection source, ref SnapshotMsg msg ) { ThreadSafe.AssertIsMainThread(); using var _ = PerformanceStats.Timings.Network.Scope(); msg.Time = Time.Now; var analytic = new Api.Events.EventRecord( "SceneNetworkSystem.GetSnapshot" ); using ( analytic.ScopeTimer( "SceneTime" ) ) { msg.SceneData = Game.ActiveScene.Serialize( _snapshotSerializeOptions ).ToJsonString(); } using ( analytic.ScopeTimer( "NetworkObjectTime" ) ) { Game.ActiveScene.SerializeNetworkObjects( msg.NetworkObjects ); } var systems = Game.ActiveScene.GetSystems(); foreach ( var system in systems ) { var type = new SnapshotMsg.GameObjectSystemData { TableData = system.WriteDataTable( true ), Type = Game.TypeLibrary.GetType( system.GetType() ).Identity, Id = system.Id }; msg.GameObjectSystems.Add( type ); } analytic.SetValue( "SceneDataLength", msg.SceneData?.Length ?? 0 ); analytic.SetValue( "NetworkObjectCount", msg.NetworkObjects?.Count ?? 0 ); analytic.SetValue( "GameObjectCount", Game.ActiveScene.Directory.GameObjectCount ); analytic.SetValue( "ComponentCount", Game.ActiveScene.Directory.ComponentCount ); analytic.SetValue( "Machine", Environment.MachineName ); analytic.Submit(); } public override void Dispose() { base.Dispose(); MountedVPKs?.Dispose(); MountedVPKs = null; if ( Instance == this ) Instance = null; } protected string WorkoutMapName() { if ( Game.ActiveScene is null ) return ""; foreach ( var map in Game.ActiveScene.GetAllComponents() ) { if ( !map.Active ) continue; if ( !map.IsLoaded ) continue; return map.MapName; } return Game.ActiveScene.Name; } protected override void Tick() { if ( !Networking.IsHost ) return; Networking.MapName = WorkoutMapName(); } private IDisposable MountedVPKs { get; set; } private async Task MountMaps( List maps ) { // Lets see if any are cloud maps and mount those first List vpks = new(); foreach ( var map in maps ) { if ( map.EndsWith( ".vpk" ) ) { vpks.Add( map ); continue; } if ( !Package.TryParseIdent( map, out var parts ) ) continue; var package = await Package.Fetch( map, false ); if ( package is null ) continue; var fs = await package.MountAsync(); if ( fs is null ) continue; var mapFileName = package.PrimaryAsset; vpks.Add( mapFileName ); } foreach ( var vpk in vpks ) g_pWorldRendererMgr.MountWorldVPK( Path.GetFileNameWithoutExtension( vpk ), Path.ChangeExtension( vpk, ".vpk" ) ); return new DisposeAction( () => { foreach ( var vpk in vpks ) g_pWorldRendererMgr.UnmountWorldVPK( Path.GetFileNameWithoutExtension( vpk ) ); } ); } /// /// We have recieved a snapshot of the world. /// public override async Task SetSnapshotAsync( SnapshotMsg msg ) { ThreadSafe.AssertIsMainThread(); if ( Game.ActiveScene is not null ) { Game.ActiveScene?.Destroy(); Game.ActiveScene = null; } Game.ActiveScene = new(); Game.ActiveScene.StartLoading(); Time.Now = (float)msg.Time; Game.ActiveScene.UpdateTimeFromHost( msg.Time ); foreach ( var s in msg.GameObjectSystems ) { var type = Game.TypeLibrary.GetTypeByIdent( s.Type ); var system = Game.ActiveScene.GetSystemByType( type ); if ( system is null ) continue; system.Id = s.Id; system.ReadDataTable( s.TableData ); } { using var batchGroup = CallbackBatch.Batch(); if ( !string.IsNullOrWhiteSpace( msg.SceneData ) ) { var sceneData = JsonNode.Parse( msg.SceneData ).AsObject(); Game.ActiveScene.Deserialize( sceneData ); } var createdNetworkObjects = new List>(); foreach ( var nwo in msg.NetworkObjects ) { if ( nwo is not ObjectCreateMsg oc ) continue; var go = new GameObject(); go.Deserialize( JsonNode.Parse( oc.JsonData ).AsObject() ); createdNetworkObjects.Add( new( go, oc ) ); } foreach ( var (go, oc) in createdNetworkObjects ) { go.NetworkSpawnRemote( oc ); } } MountedVPKs?.Dispose(); MountedVPKs = null; // Wait for loading to finish if ( Game.ActiveScene is not null ) { await Game.ActiveScene.WaitForLoading(); } if ( Game.ActiveScene.IsValid() ) { Game.ActiveScene.RunEvent( x => x.OnClientInitialize() ); } Game.IsPlaying = true; } /// /// Called on the host to decide whether to accept a . If any /// that implements this returns false, the connection will be denied. /// /// /// The reason to display to the client. public override bool AcceptConnection( Connection channel, ref string reason ) { foreach ( var c in Game.ActiveScene.GetAll() ) { if ( !c.AcceptConnection( channel, ref reason ) ) return false; } return true; } public override void OnConnected( Connection client ) { Action queue = default; foreach ( var c in Game.ActiveScene.GetAll() ) { queue += () => c.OnConnected( client ); } try { queue?.Invoke(); } catch ( Exception e ) { Log.Error( e, "Exception when calling INetworkListener.OnConnected" ); } } public override void OnInitialize() { if ( !Networking.IsHost ) return; var scene = Game.ActiveScene; if ( !scene.IsValid() || scene.IsLoading ) return; var sceneInformation = scene.Components.Get(); OnLoadedScene( sceneInformation?.Title ); } public override void OnJoined( Connection client ) { Action queue = default; foreach ( var c in Game.ActiveScene.GetAll() ) { queue += () => c.OnActive( client ); } try { queue?.Invoke(); } catch ( Exception e ) { Log.Error( e, "Exception when calling INetworkListener.OnActive" ); } } public override void OnLeave( Connection client ) { DeltaSnapshots.RemoveConnection( client ); if ( Game.ActiveScene is not null ) { foreach ( var no in Game.ActiveScene.networkedObjects ) { no.RemoveConnection( client.Id ); } foreach ( var system in Game.ActiveScene.GetSystems() ) { system.LocalSnapshotState.RemoveConnection( client.Id ); } Action queue = default; foreach ( var c in Game.ActiveScene.GetAll() ) { queue += () => c.OnDisconnected( client ); } try { queue?.Invoke(); } catch ( Exception e ) { Log.Error( e, "Exception when calling INetworkListener.OnDisconnected" ); } } if ( client.Id == Guid.Empty ) return; DoOrphanedActions( client ); } public override void OnHostChanged( Connection previousHost, Connection newHost ) { var scene = Game.ActiveScene; if ( scene.IsValid() ) { foreach ( var system in scene.GetSystems() ) { system.LocalSnapshotState.ClearConnections(); } foreach ( var no in scene.networkedObjects ) { no.ClearConnections(); } } foreach ( var connection in Connection.All ) { connection.Input.Clear(); } DeltaSnapshots?.Reset(); UserCommand.Reset(); } public override void OnBecameHost( Connection previousHost ) { // Was the host at startup, so this call isn't needed if ( previousHost is null || previousHost.Id == Guid.Empty ) return; Log.Info( $"Became the host (previous host was {previousHost})" ); var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; Action queue = default; foreach ( var c in scene.GetAll() ) { queue += () => c.OnBecameHost( previousHost ); } try { queue?.Invoke(); } catch ( Exception e ) { Log.Error( e, "Exception when calling INetworkListener.OnBecameHost" ); } // Don't run orphaned actions if the previous host is still connected. if ( previousHost.IsActive ) return; DoOrphanedActions( previousHost ); } internal void DoOrphanedActions( Connection connection ) { Game.ActiveScene?.DoOrphanedActions( connection ); } public override IDisposable Push() { return Game.ActiveScene is null ? null : Game.ActiveScene.Push(); } private void OnObjectDestroyDescendant( ObjectDestroyDescendantMsg message, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var go = scene.Directory.FindByGuid( message.Guid ); if ( !go.IsValid() ) return; var root = go.Network.RootGameObject; if ( !root.IsValid() ) return; if ( root._net is null ) { Log.Warning( $"ObjectDestroyDescendant: Object {root} is not networked" ); return; } // Only the owner or the host can do this. if ( !root._net.HasControl( source ) && !source.IsHost ) return; go.Destroy(); } private void OnObjectDestroyComponent( ObjectDestroyComponentMsg message, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var component = scene.Directory.FindComponentByGuid( message.Guid ); if ( component == null ) return; var root = component.Network.RootGameObject; if ( !root.IsValid() ) return; if ( root._net is null ) { Log.Warning( $"ObjectDestroyComponent: Object {root} is not networked" ); return; } // Only the owner or the host can do this. if ( !root._net.HasControl( source ) && !source.IsHost ) return; component.Destroy(); } private void OnObjectRefreshAck( ObjectRefreshMsgAck message, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var obj = scene.Directory.FindByGuid( message.Guid ); if ( obj is null ) return; if ( obj._net is null ) { Log.Warning( $"ObjectRefreshAck: Object {obj} is not networked" ); return; } DeltaSnapshots.ClearNetworkObject( obj._net ); } private void OnObjectRefreshDescendant( ObjectRefreshDescendantMsg message, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var parentObject = scene.Directory.FindByGuid( message.ParentId ); if ( !parentObject.IsValid() ) return; var root = parentObject.Network.RootGameObject; if ( !root.IsValid() ) return; if ( root._net is null ) { Log.Warning( $"ObjectRefreshDescendant: Object {root} is not networked" ); return; } // Only the owner or the host can do this. if ( !root._net.HasControl( source ) && !source.IsHost ) return; var gameObjectJson = JsonNode.Parse( message.JsonData ).AsObject(); if ( !gameObjectJson.TryGetPropertyValue( GameObject.JsonKeys.Id, out var childId ) ) return; var gameObject = scene.Directory.FindByGuid( childId.GetValue() ); if ( !gameObject.IsValid() ) { gameObject = new GameObject( parentObject, false ); } else if ( gameObject != parentObject ) { gameObject.SetParentFromNetwork( parentObject ); } using ( var _ = CallbackBatch.Batch() ) { gameObject?.Deserialize( gameObjectJson, new GameObject.DeserializeOptions { IsNetworkRefresh = true, IsRefreshing = true } ); } root._net.UpdateFromRefresh( source, message.TableData, message.Snapshot ); } private void OnObjectRefreshComponent( ObjectRefreshComponentMsg message, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var gameObject = scene.Directory.FindByGuid( message.GameObjectId ); if ( !gameObject.IsValid() ) return; var root = gameObject.Network.RootGameObject; if ( !root.IsValid() ) return; if ( root._net is null ) { Log.Warning( $"ObjectRefreshComponent: Object {root} is not networked" ); return; } // Only the owner or the host can do this. if ( !root._net.HasControl( source ) && !source.IsHost ) return; var componentJson = JsonNode.Parse( message.JsonData ).AsObject(); if ( !componentJson.TryGetPropertyValue( Component.JsonKeys.Id, out var componentId ) ) return; var component = scene.Directory.FindComponentByGuid( componentId.GetValue() ); if ( !component.IsValid() ) { var componentTypeName = componentJson.GetPropertyValue( Component.JsonKeys.Type, "" ); var componentType = Game.TypeLibrary.GetType( componentTypeName, true ); if ( componentType is null || componentType.TargetType.IsAbstract ) { Log.Warning( $"TypeLibrary couldn't find {nameof( Component )} type {componentTypeName}" ); return; } try { component = gameObject.Components.Create( componentType, false ); } catch ( Exception e ) { Log.Error( e ); } } else if ( component.GameObject != gameObject ) { return; } using ( var _ = CallbackBatch.Batch() ) { component?.Deserialize( componentJson ); } root._net.UpdateFromRefresh( source, message.TableData, message.Snapshot ); } private void OnObjectRefresh( ObjectRefreshMsg message, Connection source ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Refresh, message ); NetworkDebugSystem.Current?.Track( "OnObjectRefresh", message ); // Is this a request from someone? if ( source is not null && !source.CanRefreshObjects ) return; var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var obj = scene.Directory.FindByGuid( message.Guid ); if ( obj is null ) return; if ( obj._net is null ) { Log.Warning( $"ObjectRefresh: Object {obj} is not networked" ); return; } if ( obj._net.IsUnowned ) { // If we're unowned and the source is not the host, we can't refresh. if ( !source.IsHost ) return; } else { // If the source is not the owner and not the host, we can't refresh. if ( !source.IsHost && obj._net.Owner != source.Id ) return; } obj._net.OnRefreshMessage( source, message ); } private void OnObjectCreateBatch( ObjectCreateBatchMsg message, Connection source, Guid msgId ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Spawn, message ); NetworkDebugSystem.Current?.Track( "OnObjectCreateBatch", message ); // If we haven't even loaded a scene yet, this message was not sent in order (we don't even have the snapshot yet.) var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; // Is this a request from someone? if ( source is not null && !source.CanSpawnObjects ) return; using ( CallbackBatch.Batch() ) { foreach ( var msg in message.CreateMsgs ) { var go = new GameObject(); go.Deserialize( JsonNode.Parse( msg.JsonData ).AsObject() ); go.NetworkSpawnRemote( msg ); } } } private void OnObjectCreate( ObjectCreateMsg message, Connection source, Guid msgId ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Spawn, message ); NetworkDebugSystem.Current?.Track( "OnObjectCreate", message ); // If we haven't even loaded a scene yet, this message was not sent in order (we don't even have the snapshot yet.) var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; // Is this a request from someone? if ( source is not null && !source.CanSpawnObjects ) return; var go = new GameObject(); using ( CallbackBatch.Batch() ) { go.Deserialize( JsonNode.Parse( message.JsonData ).AsObject() ); go.NetworkSpawnRemote( message ); } } private void OnNetworkTableChanges( SceneNetworkTableMsg message, Connection source, Guid msgId ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.SyncVars, message ); var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var system = scene.Directory.FindSystemByGuid( message.Guid ); if ( system is null ) return; // Can we receive network table changes from this source? if ( !source.IsHost ) return; system.ReadDataTable( message.TableData ); } private void OnNetworkTableChanges( ObjectNetworkTableMsg message, Connection source, Guid msgId ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.SyncVars, message ); var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var obj = scene.Directory.FindByGuid( message.Guid ); if ( obj is null ) return; if ( obj._net is null ) { Log.Warning( $"ObjectNetworkTable: Object {obj} is not networked" ); return; } // Can we receive network table changes from this source? if ( !source.IsHost && source.Id != obj._net.Owner ) return; obj._net.OnNetworkTableMessage( message ); } private void OnObjectDestroy( ObjectDestroyMsg message, Connection source, Guid msgId ) { NetworkDebugSystem.Current?.Track( "OnObjectDestroy", message ); var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var obj = scene.Directory.FindByGuid( message.Guid ); if ( obj is null ) return; if ( obj._net is null ) { // We can't just destroy arbitrary game objects. Log.Warning( $"ObjectDestroy: Object {obj} is not networked" ); return; } if ( obj._net.IsUnowned ) { // If we're unowned and the source is not the host, we can't destroy. if ( !source.IsHost ) return; } else { // If the source is not the owner and not the host, we can't destroy. if ( !source.IsHost && obj._net.Owner != source.Id ) return; } obj._net.OnNetworkDestroy(); } private void OnObjectMessage( ObjectRpcMsg rpc, Connection source, Guid msgId ) { Rpc.IncomingInstanceRpcMsg( rpc, source ); } private void OnSceneRpc( SceneRpcMsg message, Connection source, Guid msgId ) { Rpc.IncomingInstanceRpcMsg( message, source ); } private void OnStaticRpc( StaticRpcMsg message, Connection source, Guid msgId ) { Rpc.IncomingStaticRpcMsg( message, source ); } /// /// A heartbeat has been received from the host. We should make sure our times are in sync. /// internal override void OnHeartbeat( float serverGameTime ) { Game.ActiveScene?.UpdateTimeFromHost( serverGameTime ); } /// /// We've received a cull state change for a networked object. /// internal override void OnCullStateChangeMessage( ByteStream bs, Connection source ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.Culling, bs.Length ); var objectId = bs.Read(); var isCulled = bs.Read(); var go = scene.Directory.FindByGuid( objectId ); if ( !go.IsValid() ) return; if ( go.IsNetworkCulled == isCulled ) return; var ownerId = go.Network.OwnerId; var isOwner = (source.Id == ownerId) || (ownerId == Guid.Empty && source.IsHost); if ( !isOwner ) return; go.IsNetworkCulled = isCulled; go.UpdateNetworkCulledState(); } /// /// A delta snapshot message has been received from another connection. /// internal override void OnDeltaSnapshotMessage( InternalMessageType type, ByteStream bs, Connection source ) { if ( type == InternalMessageType.DeltaSnapshot ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.SyncVars, bs.Length ); DeltaSnapshots.OnDeltaSnapshot( source, bs ); } else if ( type == InternalMessageType.DeltaSnapshotAck ) { DeltaSnapshots.OnDeltaSnapshotAck( source, bs ); } else if ( type == InternalMessageType.DeltaSnapshotCluster ) { NetworkDebugSystem.Current?.Record( NetworkDebugSystem.MessageType.SyncVars, bs.Length ); DeltaSnapshots.OnDeltaSnapshotCluster( source, bs ); } else if ( type == InternalMessageType.DeltaSnapshotClusterAck ) { DeltaSnapshots.OnDeltaSnapshotClusterAck( source, bs ); } } } /// /// When a client has sent an acknowledgement that they've received a refresh message for /// a networked object. /// [Expose] struct ObjectRefreshMsgAck { public Guid Guid { get; set; } } /// /// When a in the hierarchy of a networked object has /// been destroyed. /// [Expose] struct ObjectDestroyComponentMsg { public Guid Guid { get; set; } } /// /// When a in the hierarchy of a networked object has /// been destroyed. /// [Expose] struct ObjectDestroyDescendantMsg { public Guid Guid { get; set; } } /// /// When a in the hierarchy of a networked object has /// been added or changed. /// [Expose] struct ObjectRefreshDescendantMsg { public string JsonData { get; set; } public byte[] TableData { get; set; } public byte[] Snapshot { get; set; } public Guid ParentId { get; set; } public Guid GameObjectId { get; set; } } /// /// When a in the hierarchy of a networked object has /// been added or changed. /// [Expose] struct ObjectRefreshComponentMsg { public string JsonData { get; set; } public byte[] TableData { get; set; } public byte[] Snapshot { get; set; } public Guid GameObjectId { get; set; } } /// /// When a networked object has been refreshed. This is a full update message for that /// networked object. Any new GameObjects or Components in the hierarchy will be /// created and existing ones will be updated. /// [Expose] struct ObjectRefreshMsg { public string JsonData { get; set; } public byte[] TableData { get; set; } public byte[] Snapshot { get; set; } public Guid Parent { get; set; } public Guid Guid { get; set; } } [Expose] struct LoadSceneBeginMsg { public List MountedVPKs { get; set; } public bool ShowLoadingScreen { get; set; } public Guid Id { get; set; } } [Expose] struct LoadSceneRequestSnapshotMsg { public Guid Id { get; set; } } [Expose] struct LoadSceneSnapshotMsg { public SnapshotMsg Snapshot { get; set; } public Guid Id { get; set; } } [Expose] struct SceneLoadedMsg { public Guid Id { get; set; } } [Expose] struct ObjectCreateBatchMsg { public ObjectCreateMsg[] CreateMsgs { get; set; } } [Expose] struct ObjectCreateMsg { public ushort SnapshotVersion { get; set; } public string JsonData { get; set; } public Transform Transform { get; set; } public Guid Guid { get; set; } public Guid Creator { get; set; } public Guid Parent { get; set; } public Guid Owner { get; set; } public byte[] TableData { get; set; } public bool Enabled { get; set; } } [Expose] struct ObjectNetworkTableMsg { public Guid Guid { get; set; } public byte[] TableData { get; set; } } [Expose] struct SceneNetworkTableMsg { public Guid Guid { get; set; } public byte[] TableData { get; set; } } [Expose] struct ObjectDestroyMsg { public Guid Guid { get; set; } } [Expose] struct SceneRpcMsg { public Guid Guid { get; set; } public int MethodIdentity { get; set; } public object[] Arguments { get; set; } } [Expose] struct ObjectRpcMsg { public Guid Guid { get; set; } public Guid ComponentId { get; set; } public int MethodIdentity { get; set; } public object[] Arguments { get; set; } } [Expose] struct StaticRpcMsg { public int MethodIdentity { get; set; } public object[] Arguments { get; set; } }