mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-16 02:09:20 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
490 lines
17 KiB
C#
490 lines
17 KiB
C#
using Sandbox.Internal;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace GameObjects;
|
|
|
|
[TestClass]
|
|
public class Refresh
|
|
{
|
|
TypeLibrary TypeLibrary;
|
|
|
|
private TypeLibrary _oldTypeLibrary;
|
|
|
|
[TestInitialize]
|
|
public void TestInitialize()
|
|
{
|
|
_oldTypeLibrary = Game.TypeLibrary;
|
|
|
|
TypeLibrary = new Sandbox.Internal.TypeLibrary();
|
|
TypeLibrary.AddAssembly( typeof( ModelRenderer ).Assembly, false );
|
|
TypeLibrary.AddAssembly( typeof( ComponentIdTest ).Assembly, false );
|
|
|
|
Game.TypeLibrary = TypeLibrary;
|
|
}
|
|
|
|
[TestCleanup]
|
|
public void Cleanup()
|
|
{
|
|
Game.TypeLibrary = _oldTypeLibrary;
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RegularRefreshPrunesAllMissingObjects()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a hierarchy with parent and children
|
|
var parent = new GameObject( true, "Parent" );
|
|
|
|
// Child objects with various configurations
|
|
var child1 = new GameObject( parent, true, "Child1" );
|
|
var child2 = new GameObject( parent, true, "Child2" );
|
|
var child3 = new GameObject( parent, true, "Child3" );
|
|
|
|
// Serialize the parent to get the whole hierarchy
|
|
var originalJson = parent.Serialize();
|
|
|
|
// Remove child2 from the serialized data
|
|
var childrenArray = originalJson["Children"].AsArray();
|
|
JsonNode removedChild = null;
|
|
|
|
for ( int i = 0; i < childrenArray.Count; i++ )
|
|
{
|
|
var childJson = childrenArray[i].AsObject();
|
|
if ( childJson["Name"].GetValue<string>() == "Child2" )
|
|
{
|
|
removedChild = childrenArray[i];
|
|
childrenArray.RemoveAt( i );
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert.IsNotNull( removedChild, "Failed to find Child2 in JSON to remove" );
|
|
|
|
// Now deserialize with IsRefreshing=true, IsNetworkRefresh=false
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true,
|
|
IsNetworkRefresh = false
|
|
};
|
|
|
|
parent.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify child1 and child3 still exist, but child2 was removed
|
|
Assert.AreEqual( 2, parent.Children.Count, "Parent should have 2 children after refresh" );
|
|
|
|
var remainingNames = parent.Children.Select( c => c.Name ).ToArray();
|
|
Assert.IsTrue( remainingNames.Contains( "Child1" ), "Child1 should still exist" );
|
|
Assert.IsFalse( remainingNames.Contains( "Child2" ), "Child2 should have been removed" );
|
|
Assert.IsTrue( remainingNames.Contains( "Child3" ), "Child3 should still exist" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkRefreshPreservesNonSnapshotObjects()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a hierarchy with parent as a network object
|
|
var parent = new GameObject( true, "Parent" );
|
|
parent.NetworkMode = NetworkMode.Object;
|
|
|
|
// Child with NetworkMode.Snapshot - should be pruned if not in JSON
|
|
var snapshotChild = new GameObject( parent, true, "SnapshotChild" );
|
|
snapshotChild.NetworkMode = NetworkMode.Snapshot;
|
|
|
|
// Child with NetworkMode.Object - should be preserved
|
|
var networkObjectChild = new GameObject( parent, true, "NetworkObjectChild" );
|
|
networkObjectChild.NetworkMode = NetworkMode.Object;
|
|
|
|
// Child with NetworkMode.Snapshot but NotNetworked flag - should be preserved
|
|
var notNetworkedChild = new GameObject( parent, true, "NotNetworkedChild" );
|
|
notNetworkedChild.NetworkMode = NetworkMode.Snapshot;
|
|
notNetworkedChild.Flags |= GameObjectFlags.NotNetworked;
|
|
|
|
// Child with NetworkMode.Never - should be preserved
|
|
var neverNetworkedChild = new GameObject( parent, true, "NeverNetworkedChild" );
|
|
neverNetworkedChild.NetworkMode = NetworkMode.Never;
|
|
|
|
// Serialize the parent with SingleNetworkObject option
|
|
var serializeOptions = new GameObject.SerializeOptions
|
|
{
|
|
SingleNetworkObject = true
|
|
};
|
|
var originalJson = parent.Serialize( serializeOptions );
|
|
|
|
// Remove all children from the serialized data
|
|
var childrenArray = originalJson["Children"].AsArray();
|
|
childrenArray.Clear();
|
|
|
|
// Now deserialize with IsRefreshing=true and IsNetworkRefresh=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true,
|
|
IsNetworkRefresh = true
|
|
};
|
|
|
|
parent.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify only Snapshot objects without NotNetworked flag are removed
|
|
Assert.AreEqual( 3, parent.Children.Count, "Parent should have 3 children after network refresh" );
|
|
|
|
var remainingNames = parent.Children.Select( c => c.Name ).ToArray();
|
|
Assert.IsFalse( remainingNames.Contains( "SnapshotChild" ), "SnapshotChild should have been removed" );
|
|
Assert.IsTrue( remainingNames.Contains( "NetworkObjectChild" ), "NetworkObjectChild should still exist" );
|
|
Assert.IsTrue( remainingNames.Contains( "NotNetworkedChild" ), "NotNetworkedChild should still exist" );
|
|
Assert.IsTrue( remainingNames.Contains( "NeverNetworkedChild" ), "NeverNetworkedChild should still exist" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkRefreshPrunesComponentsInRemainingObjects()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a GameObject with NetworkMode.Object for network refresh
|
|
var go = new GameObject( true, "TestObject" );
|
|
go.NetworkMode = NetworkMode.Object;
|
|
|
|
// Add multiple components
|
|
var comp1 = go.Components.Create<ModelRenderer>();
|
|
var comp2 = go.Components.Create<ComponentIdTest>();
|
|
|
|
// Save component IDs for verification
|
|
var comp1Id = comp1.Id;
|
|
var comp2Id = comp2.Id;
|
|
|
|
// Serialize the object with SingleNetworkObject option
|
|
var serializeOptions = new GameObject.SerializeOptions
|
|
{
|
|
SingleNetworkObject = true
|
|
};
|
|
var originalJson = go.Serialize( serializeOptions );
|
|
|
|
// Remove comp2 from the serialized data
|
|
var componentsArray = originalJson["Components"].AsArray();
|
|
for ( int i = 0; i < componentsArray.Count; i++ )
|
|
{
|
|
var componentJson = componentsArray[i].AsObject();
|
|
if ( componentJson[Component.JsonKeys.Id].GetValue<Guid>() == comp2Id )
|
|
{
|
|
componentsArray.RemoveAt( i );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Deserialize with IsRefreshing=true and IsNetworkRefresh=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true,
|
|
IsNetworkRefresh = true
|
|
};
|
|
|
|
go.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify that comp1 still exists but comp2 was removed
|
|
Assert.AreEqual( 1, go.Components.Count, "Should have only one component after refresh" );
|
|
|
|
var remainingComponents = go.Components.GetAll().ToList();
|
|
Assert.AreEqual( comp1Id, remainingComponents[0].Id, "comp1 should still exist" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkRefreshPreservesHierarchyWithNestedSnapshots()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a hierarchy with network objects at different levels
|
|
var parent = new GameObject( true, "Parent" );
|
|
parent.NetworkMode = NetworkMode.Object;
|
|
|
|
// Child with NetworkMode.Snapshot - should be preserved if in JSON, pruned if not
|
|
var child1 = new GameObject( parent, true, "Child1" );
|
|
child1.NetworkMode = NetworkMode.Snapshot;
|
|
|
|
// Grandchild with NetworkMode.Snapshot - should be pruned if not in JSON
|
|
var grandchild1 = new GameObject( child1, true, "GrandChild1" );
|
|
grandchild1.NetworkMode = NetworkMode.Snapshot;
|
|
|
|
// Child with NetworkMode.Object - should be preserved
|
|
var child2 = new GameObject( parent, true, "Child2" );
|
|
child2.NetworkMode = NetworkMode.Object;
|
|
|
|
// Serialize the parent with SingleNetworkObject option
|
|
var serializeOptions = new GameObject.SerializeOptions
|
|
{
|
|
SingleNetworkObject = true
|
|
};
|
|
var originalJson = parent.Serialize( serializeOptions );
|
|
|
|
// Find Child1 in the JSON
|
|
var childrenArray = originalJson["Children"].AsArray();
|
|
JsonObject child1Json = FindChildByName( originalJson, "Child1" );
|
|
|
|
Assert.IsNotNull( child1Json, "Failed to find Child1 in JSON" );
|
|
|
|
// Remove GrandChild1 from Child1's children
|
|
var child1ChildrenArray = child1Json["Children"].AsArray();
|
|
child1ChildrenArray.Clear();
|
|
|
|
// Deserialize with IsRefreshing=true and IsNetworkRefresh=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true,
|
|
IsNetworkRefresh = true
|
|
};
|
|
|
|
parent.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify child1 still exists (it was in the JSON)
|
|
var child1After = parent.Children.FirstOrDefault( c => c.Name == "Child1" );
|
|
Assert.IsNotNull( child1After, "Child1 should still exist" );
|
|
|
|
// Verify grandchild1 was pruned (it's NetworkMode.Snapshot and wasn't in the JSON)
|
|
Assert.AreEqual( 0, child1After.Children.Count, "Child1 should have no children after refresh" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NetworkRefreshPreservesNotNetworkedComponents()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a GameObject with NetworkMode.Object for network refresh
|
|
var go = new GameObject( true, "TestObject" );
|
|
go.NetworkMode = NetworkMode.Object;
|
|
|
|
// Add multiple components
|
|
var comp1 = go.Components.Create<ModelRenderer>();
|
|
var comp2 = go.Components.Create<ComponentIdTest>();
|
|
|
|
// Mark comp2 as not networked
|
|
comp2.Flags |= ComponentFlags.NotNetworked;
|
|
|
|
// Save component IDs for verification
|
|
var comp1Id = comp1.Id;
|
|
var comp2Id = comp2.Id;
|
|
|
|
// Serialize the object with SingleNetworkObject option
|
|
var serializeOptions = new GameObject.SerializeOptions
|
|
{
|
|
SingleNetworkObject = true
|
|
};
|
|
var originalJson = go.Serialize( serializeOptions );
|
|
|
|
// Remove all components from the serialized data
|
|
originalJson["Components"] = new JsonArray();
|
|
|
|
// Deserialize with IsRefreshing=true and IsNetworkRefresh=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true,
|
|
IsNetworkRefresh = true
|
|
};
|
|
|
|
go.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify that comp1 was removed (it was networked and not in JSON)
|
|
// but comp2 was preserved (it had NotNetworked flag)
|
|
Assert.AreEqual( 1, go.Components.Count, "Should have only one component after refresh" );
|
|
|
|
var remainingComponents = go.Components.GetAll().ToList();
|
|
Assert.AreEqual( comp2Id, remainingComponents[0].Id, "NotNetworked component should be preserved" );
|
|
Assert.IsTrue( remainingComponents[0] is ComponentIdTest, "Remaining component should be ComponentIdTest" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RegularRefreshAddsNewGameObjects()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a hierarchy with parent
|
|
var parent = new GameObject( true, "Parent" );
|
|
|
|
// Serialize the parent to get the initial hierarchy
|
|
var originalJson = parent.Serialize();
|
|
|
|
// Add a new child to the JSON (not in the actual hierarchy)
|
|
var childrenArray = originalJson["Children"].AsArray();
|
|
var newChildJson = new JsonObject
|
|
{
|
|
["__guid"] = Guid.NewGuid(),
|
|
["Name"] = "NewChild",
|
|
["Position"] = JsonValue.Create( Vector3.Zero ),
|
|
["Rotation"] = JsonValue.Create( Rotation.Identity ),
|
|
["Scale"] = JsonValue.Create( Vector3.One ),
|
|
["Enabled"] = true
|
|
};
|
|
|
|
childrenArray.Add( newChildJson );
|
|
|
|
// Now deserialize with IsRefreshing=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true
|
|
};
|
|
|
|
parent.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify the new child was added
|
|
Assert.AreEqual( 1, parent.Children.Count, "Parent should have 1 child after refresh" );
|
|
Assert.AreEqual( "NewChild", parent.Children[0].Name, "New child should have been added" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RegularRefreshAddsNewComponents()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a GameObject
|
|
var go = new GameObject( true, "TestObject" );
|
|
|
|
// Add an initial component
|
|
var comp1 = go.Components.Create<ModelRenderer>();
|
|
|
|
// Serialize the object
|
|
var originalJson = go.Serialize();
|
|
|
|
// Add a new component to the JSON
|
|
var componentsArray = originalJson["Components"].AsArray();
|
|
var newComponentJson = new JsonObject
|
|
{
|
|
["__guid"] = Guid.NewGuid(),
|
|
["__type"] = "ComponentIdTest",
|
|
["__enabled"] = true
|
|
};
|
|
|
|
componentsArray.Add( newComponentJson );
|
|
|
|
// Deserialize with IsRefreshing=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true
|
|
};
|
|
|
|
go.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify both components exist
|
|
Assert.AreEqual( 2, go.Components.Count, "GameObject should have 2 components after refresh" );
|
|
Assert.IsNotNull( go.Components.Get<ModelRenderer>(), "Original component should still exist" );
|
|
Assert.IsNotNull( go.Components.Get<ComponentIdTest>(), "New component should have been added" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RegularRefreshMaintainsHierarchyRelationships()
|
|
{
|
|
using var scope = new Scene().Push();
|
|
|
|
// Create a complex hierarchy
|
|
var parent = new GameObject( true, "Parent" );
|
|
|
|
var child1 = new GameObject( parent, true, "Child1" );
|
|
var grandChild1A = new GameObject( child1, true, "GrandChild1A" );
|
|
var grandChild1B = new GameObject( child1, true, "GrandChild1B" );
|
|
|
|
var child2 = new GameObject( parent, true, "Child2" );
|
|
var grandChild2A = new GameObject( child2, true, "GrandChild2A" );
|
|
|
|
// Keep track of original hierarchy relationships and IDs
|
|
var originalHierarchy = new Dictionary<string, List<string>>();
|
|
originalHierarchy["Parent"] = parent.Children.Select( c => c.Name ).ToList();
|
|
originalHierarchy["Child1"] = child1.Children.Select( c => c.Name ).ToList();
|
|
originalHierarchy["Child2"] = child2.Children.Select( c => c.Name ).ToList();
|
|
|
|
var originalIds = new Dictionary<string, Guid>();
|
|
originalIds["Parent"] = parent.Id;
|
|
originalIds["Child1"] = child1.Id;
|
|
originalIds["Child2"] = child2.Id;
|
|
originalIds["GrandChild1A"] = grandChild1A.Id;
|
|
originalIds["GrandChild1B"] = grandChild1B.Id;
|
|
originalIds["GrandChild2A"] = grandChild2A.Id;
|
|
|
|
// Serialize the parent
|
|
var originalJson = parent.Serialize();
|
|
|
|
// Modify the JSON: remove GrandChild1B and add a new GrandChild2B
|
|
var child1Json = FindChildByName( originalJson, "Child1" );
|
|
var child1Children = child1Json["Children"].AsArray();
|
|
|
|
// Remove GrandChild1B
|
|
for ( int i = 0; i < child1Children.Count; i++ )
|
|
{
|
|
if ( child1Children[i]["Name"].GetValue<string>() == "GrandChild1B" )
|
|
{
|
|
child1Children.RemoveAt( i );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add GrandChild2B to Child2
|
|
var child2Json = FindChildByName( originalJson, "Child2" );
|
|
var child2Children = child2Json["Children"].AsArray();
|
|
|
|
var newGrandChildJson = new JsonObject
|
|
{
|
|
["__guid"] = Guid.NewGuid(),
|
|
["Name"] = "GrandChild2B",
|
|
["Position"] = JsonValue.Create( Vector3.Zero ),
|
|
["Rotation"] = JsonValue.Create( Rotation.Identity ),
|
|
["Scale"] = JsonValue.Create( Vector3.One ),
|
|
["Enabled"] = true
|
|
};
|
|
|
|
child2Children.Add( newGrandChildJson );
|
|
|
|
// Deserialize with IsRefreshing=true
|
|
var deserializeOptions = new GameObject.DeserializeOptions
|
|
{
|
|
IsRefreshing = true
|
|
};
|
|
|
|
parent.Deserialize( originalJson, deserializeOptions );
|
|
|
|
// Verify the hierarchy was updated correctly
|
|
Assert.AreEqual( 2, parent.Children.Count, "Parent should still have 2 children" );
|
|
|
|
var refreshedChild1 = parent.Children.FirstOrDefault( c => c.Name == "Child1" );
|
|
var refreshedChild2 = parent.Children.FirstOrDefault( c => c.Name == "Child2" );
|
|
|
|
Assert.IsNotNull( refreshedChild1, "Child1 should still exist" );
|
|
Assert.IsNotNull( refreshedChild2, "Child2 should still exist" );
|
|
|
|
// Child1 should have lost GrandChild1B
|
|
Assert.AreEqual( 1, refreshedChild1.Children.Count, "Child1 should have 1 child after refresh" );
|
|
Assert.AreEqual( "GrandChild1A", refreshedChild1.Children[0].Name, "GrandChild1A should still exist" );
|
|
|
|
// Child2 should have gained GrandChild2B
|
|
Assert.AreEqual( 2, refreshedChild2.Children.Count, "Child2 should have 2 children after refresh" );
|
|
|
|
var grandChildNames = refreshedChild2.Children.Select( c => c.Name ).ToArray();
|
|
Assert.IsTrue( grandChildNames.Contains( "GrandChild2A" ), "GrandChild2A should still exist" );
|
|
Assert.IsTrue( grandChildNames.Contains( "GrandChild2B" ), "GrandChild2B should have been added" );
|
|
|
|
// IDs should be preserved for existing objects
|
|
Assert.AreEqual( originalIds["Parent"], parent.Id, "Parent ID should be preserved" );
|
|
Assert.AreEqual( originalIds["Child1"], refreshedChild1.Id, "Child1 ID should be preserved" );
|
|
Assert.AreEqual( originalIds["Child2"], refreshedChild2.Id, "Child2 ID should be preserved" );
|
|
|
|
var refreshedGrandChild1A = refreshedChild1.Children.FirstOrDefault( c => c.Name == "GrandChild1A" );
|
|
Assert.AreEqual( originalIds["GrandChild1A"], refreshedGrandChild1A.Id, "GrandChild1A ID should be preserved" );
|
|
|
|
var refreshedGrandChild2A = refreshedChild2.Children.FirstOrDefault( c => c.Name == "GrandChild2A" );
|
|
Assert.AreEqual( originalIds["GrandChild2A"], refreshedGrandChild2A.Id, "GrandChild2A ID should be preserved" );
|
|
}
|
|
|
|
// Helper method to find a child object in the JSON by name
|
|
private JsonObject FindChildByName( JsonObject parentJson, string childName )
|
|
{
|
|
var childrenArray = parentJson["Children"].AsArray();
|
|
|
|
foreach ( var child in childrenArray )
|
|
{
|
|
if ( child["Name"].GetValue<string>() == childName )
|
|
{
|
|
return child.AsObject();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|