mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
Shutdown fixes (#3553)
* Stop generating solutions via -test flag add -generatesolution * Add TestAppSystem remove Application.InitUnitTest Avoids some hacks and also makes sure our tests are as close to a real AppSystem as possible. * Add shutdown unit test shuts down an re-inits the engine * Properly dispose native resources hold by managed during shutdown Should fix a bunch of crashes * Fix filesystem and networking tests * StandaloneTest does proper Game Close * Make sure package tests clean up properly * Make sure menu scene and resources are released on shutdown * Report leaked scenes on shutdown * Ensure DestroyImmediate is not used on scenes * Fix unmounting in unit tests not clearing native refs * Force destroy native resource on ResourceLib Clear
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Sandbox.Engine;
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -8,6 +9,17 @@ public static class Launcher
|
|||||||
{
|
{
|
||||||
public static int Main()
|
public static int Main()
|
||||||
{
|
{
|
||||||
|
if ( HasCommandLineSwitch( "-generatesolution" ) )
|
||||||
|
{
|
||||||
|
NetCore.InitializeInterop( Environment.CurrentDirectory );
|
||||||
|
Bootstrap.InitMinimal( Environment.CurrentDirectory );
|
||||||
|
Project.InitializeBuiltIn( false ).GetAwaiter().GetResult();
|
||||||
|
Project.GenerateSolution().GetAwaiter().GetResult();
|
||||||
|
Managed.SandboxEngine.NativeInterop.Free();
|
||||||
|
EngineFileSystem.Shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if ( !HasCommandLineSwitch( "-project" ) && !HasCommandLineSwitch( "-test" ) )
|
if ( !HasCommandLineSwitch( "-project" ) && !HasCommandLineSwitch( "-test" ) )
|
||||||
{
|
{
|
||||||
// we pass the command line, so we can pass it on to the sbox-launcher (for -game etc)
|
// we pass the command line, so we can pass it on to the sbox-launcher (for -game etc)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Sandbox.Diagnostics;
|
using Sandbox.Diagnostics;
|
||||||
using Sandbox.Engine;
|
using Sandbox.Engine;
|
||||||
using Sandbox.Internal;
|
using Sandbox.Internal;
|
||||||
|
using Sandbox.Network;
|
||||||
|
using Sandbox.Rendering;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime;
|
using System.Runtime;
|
||||||
@@ -130,15 +132,69 @@ public class AppSystem
|
|||||||
|
|
||||||
public virtual void Shutdown()
|
public virtual void Shutdown()
|
||||||
{
|
{
|
||||||
// Shut the games down
|
// Make sure game instance is closed
|
||||||
EngineLoop.Exiting();
|
IGameInstanceDll.Current?.CloseGame();
|
||||||
|
|
||||||
|
// Send shutdown event, should allow us to track successful shutdown vs crash
|
||||||
|
{
|
||||||
|
var analytic = new Api.Events.EventRecord( "Exit" );
|
||||||
|
analytic.SetValue( "uptime", RealTime.Now );
|
||||||
|
// We could record a bunch of stats during the session and
|
||||||
|
// submit them here. I'm thinking things like num games played
|
||||||
|
// menus visited, time in menus, time in game, files downloaded.
|
||||||
|
// Things to give us a whole session picture.
|
||||||
|
analytic.Submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConVarSystem.SaveAll();
|
||||||
|
|
||||||
|
IToolsDll.Current?.Exiting();
|
||||||
|
IMenuDll.Current?.Exiting();
|
||||||
|
IGameInstanceDll.Current?.Exiting();
|
||||||
|
|
||||||
|
SoundFile.Shutdown();
|
||||||
|
SoundHandle.Shutdown();
|
||||||
|
DedicatedServer.Shutdown();
|
||||||
|
|
||||||
|
// Flush API
|
||||||
|
Api.Shutdown();
|
||||||
|
|
||||||
|
ConVarSystem.ClearNativeCommands();
|
||||||
|
|
||||||
|
// Whatever package still exists needs to fuck off
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
|
||||||
|
// Clear static resources
|
||||||
|
Texture.DisposeStatic();
|
||||||
|
Model.DisposeStatic();
|
||||||
|
Material.UI.DisposeStatic();
|
||||||
|
Gizmo.GizmoDraw.DisposeStatic();
|
||||||
|
CubemapRendering.DisposeStatic();
|
||||||
|
Graphics.DisposeStatic();
|
||||||
|
|
||||||
|
TextRendering.ClearCache();
|
||||||
|
|
||||||
|
NativeResourceCache.Clear();
|
||||||
|
|
||||||
|
// Renderpipeline may hold onto native resources, clear them out
|
||||||
|
RenderPipeline.ClearPool();
|
||||||
|
|
||||||
|
// Run GC and finalizers to clear any resources held by managed
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
|
// Run the queue one more time, since some finalizers queue tasks
|
||||||
|
MainThread.RunQueues();
|
||||||
|
|
||||||
|
// print each scene that is leaked
|
||||||
|
foreach ( var leakedScene in Scene.All )
|
||||||
|
{
|
||||||
|
log.Warning( $"Leaked scene {leakedScene.Id} during shutdown." );
|
||||||
|
}
|
||||||
|
|
||||||
// Shut the engine down (close window etc)
|
// Shut the engine down (close window etc)
|
||||||
NativeEngine.EngineGlobal.SourceEngineShutdown( _appSystem, false );
|
NativeEngine.EngineGlobal.SourceEngineShutdown( _appSystem, false );
|
||||||
|
|
||||||
// Flush the api (close actvity, update stats etc)
|
|
||||||
Api.Shutdown();
|
|
||||||
|
|
||||||
if ( _appSystem.IsValid )
|
if ( _appSystem.IsValid )
|
||||||
{
|
{
|
||||||
_appSystem.Destroy();
|
_appSystem.Destroy();
|
||||||
@@ -161,11 +217,14 @@ public class AppSystem
|
|||||||
Managed.SourceHammer.NativeInterop.Free();
|
Managed.SourceHammer.NativeInterop.Free();
|
||||||
Managed.SourceModelDoc.NativeInterop.Free();
|
Managed.SourceModelDoc.NativeInterop.Free();
|
||||||
Managed.SourceAnimgraph.NativeInterop.Free();
|
Managed.SourceAnimgraph.NativeInterop.Free();
|
||||||
|
|
||||||
|
EngineFileSystem.Shutdown();
|
||||||
|
Application.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void InitGame( AppSystemCreateInfo createInfo )
|
protected void InitGame( AppSystemCreateInfo createInfo, string commandLine = null )
|
||||||
{
|
{
|
||||||
var commandLine = System.Environment.CommandLine;
|
commandLine ??= System.Environment.CommandLine;
|
||||||
commandLine = commandLine.Replace( ".dll", ".exe" ); // uck
|
commandLine = commandLine.Replace( ".dll", ".exe" ); // uck
|
||||||
|
|
||||||
_appSystem = CMaterialSystem2AppSystemDict.Create( createInfo.ToMaterialSystem2AppSystemDictCreateInfo() );
|
_appSystem = CMaterialSystem2AppSystemDict.Create( createInfo.ToMaterialSystem2AppSystemDictCreateInfo() );
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class StandaloneAppSystem : AppSystem
|
|||||||
// Quit next loop after load, if we are testing
|
// Quit next loop after load, if we are testing
|
||||||
else if ( Utility.CommandLine.HasSwitch( "-test-standalone" ) )
|
else if ( Utility.CommandLine.HasSwitch( "-test-standalone" ) )
|
||||||
{
|
{
|
||||||
Application.Exit();
|
Game.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return !wantsToQuit;
|
return !wantsToQuit;
|
||||||
|
|||||||
34
engine/Sandbox.AppSystem/TestAppSystem.cs
Normal file
34
engine/Sandbox.AppSystem/TestAppSystem.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime;
|
||||||
|
|
||||||
|
namespace Sandbox;
|
||||||
|
|
||||||
|
public class TestAppSystem : AppSystem
|
||||||
|
{
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
|
||||||
|
var GameFolder = System.Environment.GetEnvironmentVariable( "FACEPUNCH_ENGINE", EnvironmentVariableTarget.Process );
|
||||||
|
if ( GameFolder is null ) throw new Exception( "FACEPUNCH_ENGINE not found" );
|
||||||
|
|
||||||
|
NetCore.InitializeInterop( GameFolder );
|
||||||
|
|
||||||
|
var nativeDllPath = $"{GameFolder}\\bin\\win64\\";
|
||||||
|
//
|
||||||
|
// Put our native dll path first so that when looking up native dlls we'll
|
||||||
|
// always use the ones from our folder first
|
||||||
|
//
|
||||||
|
var path = System.Environment.GetEnvironmentVariable( "PATH" );
|
||||||
|
path = $"{nativeDllPath};{path}";
|
||||||
|
System.Environment.SetEnvironmentVariable( "PATH", path );
|
||||||
|
|
||||||
|
CreateGame();
|
||||||
|
|
||||||
|
var createInfo = new AppSystemCreateInfo()
|
||||||
|
{
|
||||||
|
Flags = AppSystemFlags.IsGameApp | AppSystemFlags.IsUnitTest
|
||||||
|
};
|
||||||
|
|
||||||
|
InitGame( createInfo, "" );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,12 +25,6 @@ public static class Application
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsUnitTest { get; private set; }
|
public static bool IsUnitTest { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if we're running a live unit test.
|
|
||||||
/// </summary>
|
|
||||||
internal static bool IsLiveUnitTest { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if running without a graphics window, such as in a terminal.
|
/// True if running without a graphics window, such as in a terminal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -98,106 +92,7 @@ public static class Application
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsVR => VRSystem.IsActive; // garry: I think this is right? But feels like this should be set at startup and never change?
|
public static bool IsVR => VRSystem.IsActive; // garry: I think this is right? But feels like this should be set at startup and never change?
|
||||||
|
|
||||||
static CMaterialSystem2AppSystemDict AppSystem;
|
internal static void Initialize( bool dedicated, bool headless, bool toolsMode, bool testMode, bool isRetail )
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from unit test projects to initialize the engine
|
|
||||||
/// </summary>
|
|
||||||
public static void InitUnitTest<T>( bool withtools = true, bool withRendering = false )
|
|
||||||
{
|
|
||||||
if ( IsInitialized )
|
|
||||||
throw new InvalidOperationException( "Already Initialized" );
|
|
||||||
|
|
||||||
SyncContext.Init();
|
|
||||||
SyncContext.Reset();
|
|
||||||
|
|
||||||
ThreadSafe.MarkMainThread();
|
|
||||||
|
|
||||||
var callingAssembly = Assembly.GetCallingAssembly();
|
|
||||||
var GameFolder = System.Environment.GetEnvironmentVariable( "FACEPUNCH_ENGINE", IsLiveUnitTest ? EnvironmentVariableTarget.User : EnvironmentVariableTarget.Process );
|
|
||||||
if ( GameFolder is null ) throw new Exception( "FACEPUNCH_ENGINE not found" );
|
|
||||||
|
|
||||||
var nativeDllPath = $"{GameFolder}\\bin\\win64\\";
|
|
||||||
|
|
||||||
var native = NativeLibrary.Load( $"{nativeDllPath}engine2.dll" );
|
|
||||||
|
|
||||||
//
|
|
||||||
// Put our native dll path first so that when looking up native dlls we'll
|
|
||||||
// always use the ones from our folder first
|
|
||||||
//
|
|
||||||
var path = System.Environment.GetEnvironmentVariable( "PATH" );
|
|
||||||
path = $"{nativeDllPath};{path}";
|
|
||||||
System.Environment.SetEnvironmentVariable( "PATH", path );
|
|
||||||
|
|
||||||
Api.Init();
|
|
||||||
EngineFileSystem.Initialize( GameFolder );
|
|
||||||
Application.InitializeGame( false, false, false, true, false );
|
|
||||||
NetCore.InitializeInterop( GameFolder );
|
|
||||||
|
|
||||||
Game.InitUnitTest<T>();
|
|
||||||
|
|
||||||
AppSystem = CMaterialSystem2AppSystemDict.Create( new NativeEngine.MaterialSystem2AppSystemDictCreateInfo()
|
|
||||||
{
|
|
||||||
iFlags = NativeEngine.MaterialSystem2AppSystemDictFlags.IsGameApp
|
|
||||||
} );
|
|
||||||
|
|
||||||
AppSystem.SuppressCOMInitialization();
|
|
||||||
AppSystem.SuppressStartupManifestLoad( true );
|
|
||||||
AppSystem.SetModGameSubdir( "core" );
|
|
||||||
AppSystem.SetInTestMode();
|
|
||||||
|
|
||||||
if ( withRendering )
|
|
||||||
{
|
|
||||||
AppSystem.SetDefaultRenderSystemOption( "-vulkan" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !NativeEngine.EngineGlobal.SourceEnginePreInit( "", AppSystem ) )
|
|
||||||
{
|
|
||||||
throw new System.Exception( "SourceEnginePreInit failed" );
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSystem.InitFinishSetupMaterialSystem();
|
|
||||||
|
|
||||||
AppSystem.AddSystem( "engine2", "SceneSystem_002" );
|
|
||||||
AppSystem.AddSystem( "engine2", "SceneUtils_001" );
|
|
||||||
AppSystem.AddSystem( "engine2", "WorldRendererMgr001" );
|
|
||||||
|
|
||||||
NativeLibrary.Free( native );
|
|
||||||
|
|
||||||
if ( withtools )
|
|
||||||
{
|
|
||||||
var sandboxGame = Assembly.Load( "Sandbox.Tools" );
|
|
||||||
sandboxGame.GetType( "Editor.AssemblyInitialize", true, true )
|
|
||||||
.GetMethod( "InitializeUnitTest", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic )
|
|
||||||
.Invoke( null, new[] { callingAssembly } );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void InitLiveUnitTest<T>( bool withtools = true, bool withRendering = false )
|
|
||||||
{
|
|
||||||
Application.IsLiveUnitTest = true;
|
|
||||||
InitUnitTest<T>( withtools, withRendering );
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called from unit test projects to politely shut down the engine
|
|
||||||
/// </summary>
|
|
||||||
public static void ShutdownUnitTest()
|
|
||||||
{
|
|
||||||
if ( !IsUnitTest )
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException( "Not running a unit test" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( AppSystem.IsValid )
|
|
||||||
{
|
|
||||||
NativeEngine.EngineGlobal.SourceEngineShutdown( AppSystem, false );
|
|
||||||
AppSystem.Destroy();
|
|
||||||
AppSystem = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void InitializeGame( bool dedicated, bool headless, bool toolsMode, bool testMode, bool isRetail )
|
|
||||||
{
|
{
|
||||||
if ( IsInitialized )
|
if ( IsInitialized )
|
||||||
throw new InvalidOperationException( "Already Initialized" );
|
throw new InvalidOperationException( "Already Initialized" );
|
||||||
@@ -213,6 +108,11 @@ public static class Application
|
|||||||
IsBenchmark = Environment.GetEnvironmentVariable( "SBOX_MODE" ) == "BENCHMARK";
|
IsBenchmark = Environment.GetEnvironmentVariable( "SBOX_MODE" ) == "BENCHMARK";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void Shutdown()
|
||||||
|
{
|
||||||
|
IsInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
internal static void TryLoadVersionInfo( string gameFolder )
|
internal static void TryLoadVersionInfo( string gameFolder )
|
||||||
{
|
{
|
||||||
Version = "0000000";
|
Version = "0000000";
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ internal static class Bootstrap
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static void PreInit( CMaterialSystem2AppSystemDict appDict )
|
internal static void PreInit( CMaterialSystem2AppSystemDict appDict )
|
||||||
{
|
{
|
||||||
Application.InitializeGame( appDict.IsDedicatedServer(), appDict.IsConsoleApp(), appDict.IsInToolsMode(), appDict.IsInTestMode(), EngineGlobal.IsRetail() );
|
Application.Initialize( appDict.IsDedicatedServer(), appDict.IsConsoleApp(), appDict.IsInToolsMode(), appDict.IsInTestMode(), EngineGlobal.IsRetail() );
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -91,16 +91,6 @@ internal static class Bootstrap
|
|||||||
|
|
||||||
Mounting.Directory.LoadAssemblies();
|
Mounting.Directory.LoadAssemblies();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// In testmode (-test) we want to build the .sln files now because we'll be closed
|
|
||||||
// down after this call.
|
|
||||||
//
|
|
||||||
if ( Application.IsUnitTest )
|
|
||||||
{
|
|
||||||
SyncContext.RunBlocking( Project.InitializeBuiltIn() );
|
|
||||||
SyncContext.RunBlocking( Project.GenerateSolution() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch ( Exception ex )
|
catch ( Exception ex )
|
||||||
{
|
{
|
||||||
@@ -199,9 +189,12 @@ internal static class Bootstrap
|
|||||||
//
|
//
|
||||||
{
|
{
|
||||||
Screen.UpdateFromEngine();
|
Screen.UpdateFromEngine();
|
||||||
Material.UI.Init();
|
Material.UI.InitStatic();
|
||||||
Model.Init();
|
Gizmo.GizmoDraw.InitStatic();
|
||||||
Texture.InitStaticTextures();
|
Model.InitStatic();
|
||||||
|
Texture.InitStatic();
|
||||||
|
CubemapRendering.InitStatic();
|
||||||
|
Graphics.InitStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !Application.IsHeadless && !Application.IsStandalone )
|
if ( !Application.IsHeadless && !Application.IsStandalone )
|
||||||
@@ -294,8 +287,6 @@ internal static class Bootstrap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal static void InitMinimal( string rootFolder )
|
internal static void InitMinimal( string rootFolder )
|
||||||
{
|
{
|
||||||
Environment.CurrentDirectory = rootFolder;
|
Environment.CurrentDirectory = rootFolder;
|
||||||
|
|||||||
@@ -384,34 +384,6 @@ internal static class EngineLoop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the application is shutting down
|
|
||||||
/// </summary>
|
|
||||||
internal static void Exiting()
|
|
||||||
{
|
|
||||||
// Send shutdown event, should allow us to track successful shutdown vs crash
|
|
||||||
{
|
|
||||||
var analytic = new Api.Events.EventRecord( "Exit" );
|
|
||||||
analytic.SetValue( "uptime", RealTime.Now );
|
|
||||||
// We could record a bunch of stats during the session and
|
|
||||||
// submit them here. I'm thinking things like num games played
|
|
||||||
// menus visited, time in menus, time in game, files downloaded.
|
|
||||||
// Things to give us a whole session picture.
|
|
||||||
analytic.Submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConVarSystem.SaveAll();
|
|
||||||
|
|
||||||
IToolsDll.Current?.Exiting();
|
|
||||||
IMenuDll.Current?.Exiting();
|
|
||||||
IGameInstanceDll.Current?.Exiting();
|
|
||||||
|
|
||||||
SoundFile.Shutdown();
|
|
||||||
SoundHandle.Shutdown();
|
|
||||||
|
|
||||||
DedicatedServer.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A console command has arrived, or a convar has changed
|
/// A console command has arrived, or a convar has changed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ internal partial class GlobalContext
|
|||||||
oldCts?.Cancel();
|
oldCts?.Cancel();
|
||||||
oldCts?.Dispose();
|
oldCts?.Dispose();
|
||||||
|
|
||||||
|
ActiveScene = null;
|
||||||
|
|
||||||
TaskSource = new TaskSource( 1 );
|
TaskSource = new TaskSource( 1 );
|
||||||
|
|
||||||
EventSystem?.Dispose();
|
EventSystem?.Dispose();
|
||||||
@@ -133,6 +135,9 @@ internal partial class GlobalContext
|
|||||||
|
|
||||||
Cookies?.Dispose();
|
Cookies?.Dispose();
|
||||||
Cookies = null;
|
Cookies = null;
|
||||||
|
|
||||||
|
ResourceSystem?.Clear();
|
||||||
|
ResourceSystem = new ResourceSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
string _disabledReason;
|
string _disabledReason;
|
||||||
|
|||||||
@@ -15,10 +15,30 @@ public static partial class Gizmo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class GizmoDraw
|
public sealed partial class GizmoDraw
|
||||||
{
|
{
|
||||||
static Material LineMaterial = Material.Load( "materials/gizmo/line.vmat" );
|
static Material LineMaterial;
|
||||||
static Material SolidMaterial = Material.Load( "materials/gizmo/solid.vmat" );
|
static Material SolidMaterial;
|
||||||
static Material SpriteMaterial = Material.Load( "materials/gizmo/sprite.vmat" );
|
static Material SpriteMaterial;
|
||||||
static Material GridMaterial = Material.Load( "materials/gizmo/grid.vmat" );
|
static Material GridMaterial;
|
||||||
|
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
LineMaterial = Material.Load( "materials/gizmo/line.vmat" );
|
||||||
|
SolidMaterial = Material.Load( "materials/gizmo/solid.vmat" );
|
||||||
|
SpriteMaterial = Material.Load( "materials/gizmo/sprite.vmat" );
|
||||||
|
GridMaterial = Material.Load( "materials/gizmo/grid.vmat" );
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
LineMaterial?.Dispose();
|
||||||
|
LineMaterial = null;
|
||||||
|
SolidMaterial?.Dispose();
|
||||||
|
SolidMaterial = null;
|
||||||
|
SpriteMaterial?.Dispose();
|
||||||
|
SpriteMaterial = null;
|
||||||
|
GridMaterial?.Dispose();
|
||||||
|
GridMaterial = null;
|
||||||
|
}
|
||||||
|
|
||||||
internal GizmoDraw()
|
internal GizmoDraw()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,24 +47,6 @@ public static partial class Game
|
|||||||
set => GlobalContext.Current.NodeLibrary = value;
|
set => GlobalContext.Current.NodeLibrary = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize for a unit test
|
|
||||||
/// </summary>
|
|
||||||
internal static void InitUnitTest<T>()
|
|
||||||
{
|
|
||||||
GlobalContext.Current.Reset();
|
|
||||||
GlobalContext.Current.LocalAssembly = typeof( T ).Assembly;
|
|
||||||
GlobalContext.Current.UISystem = new UISystem();
|
|
||||||
GlobalContext.Current.InputContext = new InputContext();
|
|
||||||
GlobalContext.Current.InputContext.TargetUISystem = GlobalContext.Current.UISystem;
|
|
||||||
|
|
||||||
Game.InitHost();
|
|
||||||
|
|
||||||
TypeLibrary.AddAssembly( typeof( T ).Assembly, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void AddNodesFromAssembly( Assembly asm )
|
private static void AddNodesFromAssembly( Assembly asm )
|
||||||
{
|
{
|
||||||
var result = NodeLibrary.AddAssembly( asm );
|
var result = NodeLibrary.AddAssembly( asm );
|
||||||
|
|||||||
@@ -141,21 +141,21 @@ public static partial class Game
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Might want to queue this up. do it the next frame?
|
||||||
|
// Be aware that this could be called from the GameDll or the MenuDll
|
||||||
|
// So anything here needs to be safe to call from either
|
||||||
|
|
||||||
|
if ( IGameInstance.Current is not null )
|
||||||
|
{
|
||||||
|
IGameInstance.Current.Close();
|
||||||
|
LaunchArguments.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Standalone mode and Dedicated Server only: exit whole app
|
// Standalone mode and Dedicated Server only: exit whole app
|
||||||
if ( Application.IsStandalone || Application.IsDedicatedServer )
|
if ( Application.IsStandalone || Application.IsDedicatedServer )
|
||||||
{
|
{
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Might want to queue this up. do it the next frame?
|
|
||||||
// Be aware that this could be called from the GameDll or the MenuDll
|
|
||||||
// So anything here needs to be safe to call from either
|
|
||||||
|
|
||||||
if ( IGameInstance.Current is null )
|
|
||||||
return;
|
|
||||||
|
|
||||||
IGameInstance.Current.Close();
|
|
||||||
LaunchArguments.Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Sandbox
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static Material DropShadow { get; set; }
|
internal static Material DropShadow { get; set; }
|
||||||
|
|
||||||
internal static void Init()
|
internal static void InitStatic()
|
||||||
{
|
{
|
||||||
Basic = FromShader( "shaders/ui_basic.shader" );
|
Basic = FromShader( "shaders/ui_basic.shader" );
|
||||||
Box = FromShader( "shaders/ui_cssbox.shader" );
|
Box = FromShader( "shaders/ui_cssbox.shader" );
|
||||||
@@ -51,6 +51,26 @@ namespace Sandbox
|
|||||||
DropShadow = FromShader( "shaders/ui_dropshadow.shader" );
|
DropShadow = FromShader( "shaders/ui_dropshadow.shader" );
|
||||||
BorderWrap = FromShader( "shaders/ui_borderwrap.shader" );
|
BorderWrap = FromShader( "shaders/ui_borderwrap.shader" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
Basic?.Dispose();
|
||||||
|
Basic = null;
|
||||||
|
Box?.Dispose();
|
||||||
|
Box = null;
|
||||||
|
BoxShadow?.Dispose();
|
||||||
|
BoxShadow = null;
|
||||||
|
Text?.Dispose();
|
||||||
|
Text = null;
|
||||||
|
BackdropFilter?.Dispose();
|
||||||
|
BackdropFilter = null;
|
||||||
|
Filter?.Dispose();
|
||||||
|
Filter = null;
|
||||||
|
DropShadow?.Dispose();
|
||||||
|
DropShadow = null;
|
||||||
|
BorderWrap?.Dispose();
|
||||||
|
BorderWrap = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,16 +41,24 @@ public sealed partial class Material : Resource
|
|||||||
}
|
}
|
||||||
|
|
||||||
~Material()
|
~Material()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Dispose()
|
||||||
{
|
{
|
||||||
// kill the native pointer - it does with the native material
|
// kill the native pointer - it does with the native material
|
||||||
// we want to reduce the risk that someone is holding on to it.
|
// we want to reduce the risk that someone is holding on to it.
|
||||||
Attributes.Set( default );
|
Attributes?.Set( default );
|
||||||
Attributes = null;
|
Attributes = null;
|
||||||
|
|
||||||
var n = native;
|
if ( !native.IsNull )
|
||||||
native = default;
|
{
|
||||||
|
var n = native;
|
||||||
|
native = default;
|
||||||
|
|
||||||
MainThread.Queue( () => n.DestroyStrongHandle() );
|
MainThread.Queue( () => n.DestroyStrongHandle() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -58,11 +58,23 @@ public partial class Model
|
|||||||
public static Model Error { get; internal set; }
|
public static Model Error { get; internal set; }
|
||||||
|
|
||||||
|
|
||||||
internal static void Init()
|
internal static void InitStatic()
|
||||||
{
|
{
|
||||||
Cube = Load( "models/dev/box.vmdl" );
|
Cube = Load( "models/dev/box.vmdl" );
|
||||||
Sphere = Load( "models/dev/sphere.vmdl" );
|
Sphere = Load( "models/dev/sphere.vmdl" );
|
||||||
Plane = Load( "models/dev/plane.vmdl" );
|
Plane = Load( "models/dev/plane.vmdl" );
|
||||||
Error = Load( "models/dev/error.vmdl" );
|
Error = Load( "models/dev/error.vmdl" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
Cube?.Dispose();
|
||||||
|
Cube = null;
|
||||||
|
Sphere?.Dispose();
|
||||||
|
Sphere = null;
|
||||||
|
Plane?.Dispose();
|
||||||
|
Plane = null;
|
||||||
|
Error?.Dispose();
|
||||||
|
Error = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,20 @@ public sealed partial class Model : Resource
|
|||||||
SetIdFromResourcePath( Name );
|
SetIdFromResourcePath( Name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Dispose()
|
||||||
|
{
|
||||||
|
if ( !native.IsNull )
|
||||||
|
{
|
||||||
|
var n = native;
|
||||||
|
native = default;
|
||||||
|
|
||||||
|
MainThread.Queue( () => n.DestroyStrongHandle() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
~Model()
|
~Model()
|
||||||
{
|
{
|
||||||
var n = native;
|
Dispose();
|
||||||
native = default;
|
|
||||||
|
|
||||||
MainThread.Queue( () => n.DestroyStrongHandle() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -39,13 +39,20 @@ public abstract partial class Resource : IValid, IJsonConvert, BytePack.ISeriali
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Hide, JsonIgnore] public virtual bool HasUnsavedChanges => false;
|
[Hide, JsonIgnore] public virtual bool HasUnsavedChanges => false;
|
||||||
|
|
||||||
~Resource()
|
internal void Destroy()
|
||||||
{
|
{
|
||||||
// Unregister on main thread
|
// Unregister on main thread
|
||||||
MainThread.Queue( () => { Game.Resources.Unregister( this ); } );
|
MainThread.Queue( () => { Game.Resources.Unregister( this ); } );
|
||||||
|
|
||||||
Manifest?.Dispose();
|
Manifest?.Dispose();
|
||||||
Manifest = default;
|
Manifest = default;
|
||||||
|
|
||||||
|
GC.SuppressFinalize( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
~Resource()
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string FixPath( string filename )
|
internal static string FixPath( string filename )
|
||||||
|
|||||||
@@ -58,13 +58,19 @@ public class ResourceSystem
|
|||||||
|
|
||||||
var toDispose = ResourceIndex.Values.ToArray();
|
var toDispose = ResourceIndex.Values.ToArray();
|
||||||
|
|
||||||
ResourceIndex.Clear();
|
|
||||||
|
|
||||||
foreach ( var resource in toDispose.OfType<GameResource>() )
|
foreach ( var resource in toDispose.OfType<GameResource>() )
|
||||||
{
|
{
|
||||||
resource.DestroyInternal();
|
resource.DestroyInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ( var resource in toDispose )
|
||||||
|
{
|
||||||
|
// Don't wait/rely for finalizer get rid of this immediately
|
||||||
|
resource.Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceIndex.Clear();
|
||||||
|
|
||||||
TypeCache.Clear();
|
TypeCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public partial class PrefabFile : GameResource
|
|||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
CachedScene?.DestroyImmediate();
|
CachedScene?.DestroyInternal();
|
||||||
CachedScene = null;
|
CachedScene = null;
|
||||||
|
|
||||||
Unregister();
|
Unregister();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public partial class Texture
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static Texture Transparent { get; internal set; }
|
public static Texture Transparent { get; internal set; }
|
||||||
|
|
||||||
internal static void InitStaticTextures()
|
internal static void InitStatic()
|
||||||
{
|
{
|
||||||
Invalid = Create( 1, 1 ).WithData( new byte[4] { 255, 0, 255, 255 } ).Finish();
|
Invalid = Create( 1, 1 ).WithData( new byte[4] { 255, 0, 255, 255 } ).Finish();
|
||||||
White = Create( 1, 1 ).WithData( new byte[4] { 255, 255, 255, 255 } ).Finish();
|
White = Create( 1, 1 ).WithData( new byte[4] { 255, 255, 255, 255 } ).Finish();
|
||||||
@@ -31,6 +31,18 @@ public partial class Texture
|
|||||||
Black = Create( 1, 1 ).WithData( new byte[4] { 0, 0, 0, 255 } ).Finish();
|
Black = Create( 1, 1 ).WithData( new byte[4] { 0, 0, 0, 255 } ).Finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
Invalid?.Dispose();
|
||||||
|
Invalid = default;
|
||||||
|
White?.Dispose();
|
||||||
|
White = default;
|
||||||
|
Transparent?.Dispose();
|
||||||
|
Transparent = default;
|
||||||
|
Black?.Dispose();
|
||||||
|
Black = default;
|
||||||
|
}
|
||||||
|
|
||||||
internal static Texture Create( string name, bool anonymous, TextureBuilder builder, IntPtr data, int dataSize )
|
internal static Texture Create( string name, bool anonymous, TextureBuilder builder, IntPtr data, int dataSize )
|
||||||
{
|
{
|
||||||
var config = builder._config.GetWithFixes();
|
var config = builder._config.GetWithFixes();
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public sealed partial class PolygonMesh : IJsonConvert
|
|||||||
public int MaterialId { get; set; }
|
public int MaterialId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Material DefaultMaterial = Material.Load( "materials/dev/reflectivity_30.vmat" );
|
private Material DefaultMaterial = Material.Load( "materials/dev/reflectivity_30.vmat" );
|
||||||
private static readonly Vector2 DefaultTextureSize = CalculateTextureSize( DefaultMaterial );
|
private Vector2 DefaultTextureSize => CalculateTextureSize( DefaultMaterial );
|
||||||
|
|
||||||
private VertexData<Vector3> Positions { get; init; }
|
private VertexData<Vector3> Positions { get; init; }
|
||||||
private HalfEdgeData<Vector2> TextureCoord { get; init; }
|
private HalfEdgeData<Vector2> TextureCoord { get; init; }
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ public partial class GameObject : IValid
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void DestroyImmediate()
|
public void DestroyImmediate()
|
||||||
{
|
{
|
||||||
|
Assert.False( this is Scene, "Don't call DestroyImmediate on a scene." );
|
||||||
ThreadSafe.AssertIsMainThread();
|
ThreadSafe.AssertIsMainThread();
|
||||||
|
|
||||||
Term();
|
Term();
|
||||||
|
|||||||
@@ -234,10 +234,10 @@ public partial class SceneNetworkSystem : GameNetworkSystem
|
|||||||
LoadingScreen.Title = "Loading Scene";
|
LoadingScreen.Title = "Loading Scene";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go ahead and destroy the scene immediately (if it exists.)
|
// Go ahead and destroy the scene
|
||||||
if ( Game.ActiveScene is not null )
|
if ( Game.ActiveScene is not null )
|
||||||
{
|
{
|
||||||
Game.ActiveScene?.DestroyImmediate();
|
Game.ActiveScene?.Destroy();
|
||||||
Game.ActiveScene = null;
|
Game.ActiveScene = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ public partial class SceneNetworkSystem : GameNetworkSystem
|
|||||||
|
|
||||||
if ( Game.ActiveScene is not null )
|
if ( Game.ActiveScene is not null )
|
||||||
{
|
{
|
||||||
Game.ActiveScene?.DestroyImmediate();
|
Game.ActiveScene?.Destroy();
|
||||||
Game.ActiveScene = null;
|
Game.ActiveScene = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ public partial class Scene : GameObject
|
|||||||
|
|
||||||
internal virtual void DestroyInternal()
|
internal virtual void DestroyInternal()
|
||||||
{
|
{
|
||||||
|
_all.Remove( this );
|
||||||
|
|
||||||
// Clearing the object index now means we can save time
|
// Clearing the object index now means we can save time
|
||||||
// because we don't have to do it for each object.
|
// because we don't have to do it for each object.
|
||||||
// Note that we can't do this in Clear because we don't want to
|
// Note that we can't do this in Clear because we don't want to
|
||||||
@@ -154,7 +156,6 @@ public partial class Scene : GameObject
|
|||||||
public IDisposable Push()
|
public IDisposable Push()
|
||||||
{
|
{
|
||||||
ThreadSafe.AssertIsMainThread();
|
ThreadSafe.AssertIsMainThread();
|
||||||
|
|
||||||
var old = Game.ActiveScene;
|
var old = Game.ActiveScene;
|
||||||
|
|
||||||
Game.ActiveScene = this;
|
Game.ActiveScene = this;
|
||||||
|
|||||||
@@ -170,9 +170,6 @@ internal static partial class PackageManager
|
|||||||
MountedFileSystem.Mount( FileSystem );
|
MountedFileSystem.Mount( FileSystem );
|
||||||
MountedFileSystem.Mount( AssemblyFileSystem );
|
MountedFileSystem.Mount( AssemblyFileSystem );
|
||||||
|
|
||||||
if ( Application.IsUnitTest ) // todo: fully init the engine for unit test
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Reload any already resident resources with the ones we've just mounted
|
// Reload any already resident resources with the ones we've just mounted
|
||||||
NativeEngine.g_pResourceSystem.ReloadSymlinkedResidentResources();
|
NativeEngine.g_pResourceSystem.ReloadSymlinkedResidentResources();
|
||||||
|
|
||||||
@@ -199,9 +196,6 @@ internal static partial class PackageManager
|
|||||||
AssemblyFileSystem.Dispose();
|
AssemblyFileSystem.Dispose();
|
||||||
AssemblyFileSystem = null;
|
AssemblyFileSystem = null;
|
||||||
|
|
||||||
if ( Application.IsUnitTest ) // todo: fully init the engine for unit test
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Reload any resident resources that were just unmounted (they shouldn't be used & will appear as an error, or a local variant)
|
// Reload any resident resources that were just unmounted (they shouldn't be used & will appear as an error, or a local variant)
|
||||||
NativeEngine.g_pResourceSystem.ReloadSymlinkedResidentResources();
|
NativeEngine.g_pResourceSystem.ReloadSymlinkedResidentResources();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,6 @@ internal static partial class PackageManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static event Action<ActivePackage, string> OnPackageInstalledToContext;
|
public static event Action<ActivePackage, string> OnPackageInstalledToContext;
|
||||||
|
|
||||||
internal static void ResetForUnitTest()
|
|
||||||
{
|
|
||||||
ActivePackages = new();
|
|
||||||
MountedFileSystem = new AggregateFileSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<Package> FetchPackageAsync( string ident, bool localPriority )
|
static async Task<Package> FetchPackageAsync( string ident, bool localPriority )
|
||||||
{
|
{
|
||||||
if ( localPriority && Package.TryParseIdent( ident, out var parts ) && !parts.local )
|
if ( localPriority && Package.TryParseIdent( ident, out var parts ) && !parts.local )
|
||||||
@@ -131,6 +125,15 @@ internal static partial class PackageManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void UnmountAll()
|
||||||
|
{
|
||||||
|
foreach ( var item in ActivePackages.ToArray() )
|
||||||
|
{
|
||||||
|
item.Delete();
|
||||||
|
ActivePackages.Remove( item );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task InstallDependencies( Package package, PackageLoadOptions options )
|
private static async Task InstallDependencies( Package package, PackageLoadOptions options )
|
||||||
{
|
{
|
||||||
HashSet<string> dependancies = new HashSet<string>( StringComparer.OrdinalIgnoreCase );
|
HashSet<string> dependancies = new HashSet<string>( StringComparer.OrdinalIgnoreCase );
|
||||||
|
|||||||
@@ -19,6 +19,31 @@ internal static partial class ConVarSystem
|
|||||||
var command = new NativeCommand( value );
|
var command = new NativeCommand( value );
|
||||||
AddCommand( command );
|
AddCommand( command );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void ClearNativeCommands()
|
||||||
|
{
|
||||||
|
if ( Members.Count == 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
System.Collections.Generic.List<string> nativeKeys = null;
|
||||||
|
|
||||||
|
foreach ( var (name, command) in Members )
|
||||||
|
{
|
||||||
|
if ( command is NativeCommand || command is NativeConVar )
|
||||||
|
{
|
||||||
|
nativeKeys ??= new System.Collections.Generic.List<string>();
|
||||||
|
nativeKeys.Add( name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( nativeKeys is null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach ( var name in nativeKeys )
|
||||||
|
{
|
||||||
|
Members.Remove( name );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ internal static class EngineFileSystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Don't try to use the filesystem until you've called this!
|
/// Don't try to use the filesystem until you've called this!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static void Initialize( string rootFolder )
|
internal static void Initialize( string rootFolder, bool skipBaseFolderInit = false )
|
||||||
{
|
{
|
||||||
if ( Root != null )
|
if ( Root != null )
|
||||||
throw new System.Exception( "Filesystem Multi-Initialize" );
|
throw new System.Exception( "Filesystem Multi-Initialize" );
|
||||||
@@ -52,8 +52,7 @@ internal static class EngineFileSystem
|
|||||||
Root = new LocalFileSystem( rootFolder );
|
Root = new LocalFileSystem( rootFolder );
|
||||||
Temporary = new MemoryFileSystem();
|
Temporary = new MemoryFileSystem();
|
||||||
|
|
||||||
if ( Application.IsUnitTest )
|
if ( skipBaseFolderInit ) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if ( Application.IsEditor )
|
if ( Application.IsEditor )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ internal partial class NetworkSystem
|
|||||||
|
|
||||||
public void InitializeGameSystem()
|
public void InitializeGameSystem()
|
||||||
{
|
{
|
||||||
if ( IGameInstanceDll.Current is null )
|
// If we are unit testing we dont want to do any of this for now, this only works with a gamepackage loaded
|
||||||
|
if ( IGameInstanceDll.Current is null || Application.IsUnitTest )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GameSystem = IGameInstanceDll.Current.CreateGameNetworking( this );
|
GameSystem = IGameInstanceDll.Current.CreateGameNetworking( this );
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Sandbox;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ServerPackages
|
internal class ServerPackages
|
||||||
{
|
{
|
||||||
public static ServerPackages Current { get; private set; }
|
public static ServerPackages Current { get; private set; } = new();
|
||||||
|
|
||||||
internal record struct ServerPackageInfo();
|
internal record struct ServerPackageInfo();
|
||||||
internal StringTable StringTable;
|
internal StringTable StringTable;
|
||||||
@@ -86,7 +86,7 @@ internal class ServerPackages
|
|||||||
|
|
||||||
internal ServerPackages()
|
internal ServerPackages()
|
||||||
{
|
{
|
||||||
Assert.IsNull( Current );
|
// WTF???
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
StringTable = new StringTable( "ServerPackages", true );
|
StringTable = new StringTable( "ServerPackages", true );
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using NativeEngine;
|
using NativeEngine;
|
||||||
|
using Sandbox.VR;
|
||||||
|
|
||||||
namespace Sandbox;
|
namespace Sandbox;
|
||||||
|
|
||||||
@@ -25,6 +26,11 @@ public class ComputeShader
|
|||||||
ComputeMaterial = material;
|
ComputeMaterial = material;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Dispose()
|
||||||
|
{
|
||||||
|
ComputeMaterial.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispatch this compute shader using explicit thread counts.
|
/// Dispatch this compute shader using explicit thread counts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using NativeEngine;
|
|
||||||
using System.Linq;
|
|
||||||
using Sandbox.Rendering;
|
using Sandbox.Rendering;
|
||||||
|
|
||||||
namespace Sandbox;
|
namespace Sandbox;
|
||||||
@@ -10,7 +8,18 @@ namespace Sandbox;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class CubemapRendering
|
internal static class CubemapRendering
|
||||||
{
|
{
|
||||||
static ComputeShader EnvmapFilter = new( "envmap_filtering_cs" );
|
static ComputeShader EnvmapFilter;
|
||||||
|
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
EnvmapFilter = new( "envmap_filtering_cs" );
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
EnvmapFilter?.Dispose();
|
||||||
|
EnvmapFilter = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the quality level for GGX filtering of environment maps.
|
/// Specifies the quality level for GGX filtering of environment maps.
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ namespace Sandbox;
|
|||||||
|
|
||||||
public static partial class Graphics
|
public static partial class Graphics
|
||||||
{
|
{
|
||||||
internal static ComputeShader MipMapGeneratorShader = new ComputeShader( "downsample_cs" );
|
internal static ComputeShader MipMapGeneratorShader;
|
||||||
|
|
||||||
|
internal static void InitStatic()
|
||||||
|
{
|
||||||
|
MipMapGeneratorShader = new ComputeShader( "downsample_cs" );
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void DisposeStatic()
|
||||||
|
{
|
||||||
|
MipMapGeneratorShader?.Dispose();
|
||||||
|
MipMapGeneratorShader = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Which method to use when downsampling a texture
|
/// Which method to use when downsampling a texture
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ internal class BloomDownsampleLayer : ProceduralRenderLayer
|
|||||||
}
|
}
|
||||||
internal class QuarterDepthDownsampleLayer : ProceduralRenderLayer
|
internal class QuarterDepthDownsampleLayer : ProceduralRenderLayer
|
||||||
{
|
{
|
||||||
private static Material DepthResolve = Material.Create( "depthresolve", "shaders/depthresolve.shader" );
|
private Material DepthResolve;
|
||||||
private bool MSAAInput;
|
private bool MSAAInput;
|
||||||
|
|
||||||
public QuarterDepthDownsampleLayer()
|
public QuarterDepthDownsampleLayer()
|
||||||
@@ -55,6 +55,7 @@ internal class QuarterDepthDownsampleLayer : ProceduralRenderLayer
|
|||||||
Flags |= LayerFlags.NeverRemove | LayerFlags.DoesntModifyColorBuffers;
|
Flags |= LayerFlags.NeverRemove | LayerFlags.DoesntModifyColorBuffers;
|
||||||
ClearFlags = ClearFlags.Depth | ClearFlags.Stencil;
|
ClearFlags = ClearFlags.Depth | ClearFlags.Stencil;
|
||||||
LayerType = SceneLayerType.Opaque;
|
LayerType = SceneLayerType.Opaque;
|
||||||
|
DepthResolve = Material.Create( "depthresolve", "shaders/depthresolve.shader" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Setup( ISceneView view, RenderViewport viewport, SceneViewRenderTargetHandle rtDepth, bool msaaInput, RenderTarget rtOutDepth )
|
public void Setup( ISceneView view, RenderViewport viewport, SceneViewRenderTargetHandle rtDepth, bool msaaInput, RenderTarget rtOutDepth )
|
||||||
|
|||||||
@@ -43,4 +43,10 @@ internal partial class RenderPipeline
|
|||||||
// Return to pool
|
// Return to pool
|
||||||
Pool.Enqueue( renderPipeline );
|
Pool.Enqueue( renderPipeline );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void ClearPool()
|
||||||
|
{
|
||||||
|
Pool.Clear();
|
||||||
|
ActivePipelines.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,4 +95,13 @@ public static partial class TextRendering
|
|||||||
|
|
||||||
//Log.Info( $"TextManager: {total} ({deleted} deleted)" );
|
//Log.Info( $"TextManager: {total} ({deleted} deleted)" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void ClearCache()
|
||||||
|
{
|
||||||
|
foreach ( var item in Dictionary )
|
||||||
|
{
|
||||||
|
item.Value.Dispose();
|
||||||
|
}
|
||||||
|
Dictionary.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
|
|||||||
[assembly: TasksPersistOnContextReset]
|
[assembly: TasksPersistOnContextReset]
|
||||||
[assembly: InternalsVisibleTo( "Sandbox.Tools" )]
|
[assembly: InternalsVisibleTo( "Sandbox.Tools" )]
|
||||||
[assembly: InternalsVisibleTo( "Sandbox.AppSystem" )]
|
[assembly: InternalsVisibleTo( "Sandbox.AppSystem" )]
|
||||||
|
[assembly: InternalsVisibleTo( "Sandbox.Test" )]
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ internal partial class GameInstance : IGameInstance
|
|||||||
|
|
||||||
if ( activePackage != null && !Application.IsStandalone )
|
if ( activePackage != null && !Application.IsStandalone )
|
||||||
{
|
{
|
||||||
Game.Language.Shutdown();
|
Game.Language?.Shutdown();
|
||||||
FileSystem.Mounted.UnMount( activePackage.FileSystem );
|
FileSystem.Mounted?.UnMount( activePackage.FileSystem );
|
||||||
|
|
||||||
activePackage = null;
|
activePackage = null;
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ internal partial class GameInstance : IGameInstance
|
|||||||
// Is this the right place for it? Map packages are marked with "game" so they never get unmounted
|
// Is this the right place for it? Map packages are marked with "game" so they never get unmounted
|
||||||
PackageManager.UnmountTagged( "game" );
|
PackageManager.UnmountTagged( "game" );
|
||||||
|
|
||||||
GameInstanceDll.Current.OnGameInstanceClosed( this );
|
GameInstanceDll.Current.Shutdown( this );
|
||||||
|
|
||||||
Game.Shutdown();
|
Game.Shutdown();
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ internal partial class GameInstanceDll : Engine.IGameInstanceDll
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsNull( PackageLoader );
|
PackageLoader?.Dispose();
|
||||||
PackageLoader = new PackageLoader( "GameMenu", typeof( GameInstanceDll ).Assembly );
|
PackageLoader = new PackageLoader( "GameMenu", typeof( GameInstanceDll ).Assembly );
|
||||||
PackageLoader.HotloadWatch( Game.GameAssembly ); // Sandbox.Game is per instance
|
PackageLoader.HotloadWatch( Game.GameAssembly ); // Sandbox.Game is per instance
|
||||||
PackageLoader.OnAfterHotload = OnAfterHotload;
|
PackageLoader.OnAfterHotload = OnAfterHotload;
|
||||||
@@ -234,11 +234,14 @@ internal partial class GameInstanceDll : Engine.IGameInstanceDll
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear resource library so resources don't leak between games,
|
|
||||||
// then let IMenuDll reload whatever resources it needs
|
|
||||||
|
|
||||||
Game.Resources.Clear();
|
|
||||||
IMenuDll.Current?.Reset();
|
IMenuDll.Current?.Reset();
|
||||||
|
|
||||||
|
// Run GC and finalizers to clear any native resources held
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
|
// Run the queue one more time, since some finalizers queue tasks
|
||||||
|
MainThread.RunQueues();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -700,7 +703,7 @@ internal partial class GameInstanceDll : Engine.IGameInstanceDll
|
|||||||
/// Called when the game menu is closed
|
/// Called when the game menu is closed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="instance"></param>
|
/// <param name="instance"></param>
|
||||||
public void OnGameInstanceClosed( IGameInstance instance )
|
public void Shutdown( IGameInstance instance )
|
||||||
{
|
{
|
||||||
NativeErrorReporter.Breadcrumb( true, "game", "Closed game instance" );
|
NativeErrorReporter.Breadcrumb( true, "game", "Closed game instance" );
|
||||||
NativeErrorReporter.SetTag( "game", null );
|
NativeErrorReporter.SetTag( "game", null );
|
||||||
|
|||||||
@@ -211,8 +211,44 @@ internal sealed class MenuDll : IMenuDll
|
|||||||
|
|
||||||
public void Exiting()
|
public void Exiting()
|
||||||
{
|
{
|
||||||
using var scope = PushScope();
|
using ( PushScope() )
|
||||||
Game.Cookies?.Save();
|
{
|
||||||
|
// Shutdown menu system
|
||||||
|
IMenuSystem.Current?.Shutdown();
|
||||||
|
IMenuSystem.Current = null;
|
||||||
|
|
||||||
|
// Unregister messaging
|
||||||
|
Sandbox.Services.Messaging.OnMessage -= OnMessageFromBackend;
|
||||||
|
|
||||||
|
// Save and dispose cookies
|
||||||
|
Game.Cookies?.Save();
|
||||||
|
Game.Cookies = null;
|
||||||
|
|
||||||
|
// Cleanup scene
|
||||||
|
MenuScene.Scene?.Destroy();
|
||||||
|
MenuScene.Scene = null;
|
||||||
|
|
||||||
|
// Dispose package loader and enroller
|
||||||
|
Enroller?.Dispose();
|
||||||
|
Enroller = null;
|
||||||
|
|
||||||
|
Loader?.Dispose();
|
||||||
|
Loader = null;
|
||||||
|
|
||||||
|
// Shutdown Steamworks interfaces
|
||||||
|
if ( !Application.IsEditor )
|
||||||
|
{
|
||||||
|
Steamworks.SteamClient.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire async context to prevent lingering tasks
|
||||||
|
AsyncContext.Expire( null );
|
||||||
|
|
||||||
|
// Clear global context
|
||||||
|
GlobalContext.Current.Reset();
|
||||||
|
|
||||||
|
IMenuDll.Current = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadResources()
|
void LoadResources()
|
||||||
|
|||||||
@@ -2,123 +2,78 @@
|
|||||||
using Sandbox.ActionGraphs;
|
using Sandbox.ActionGraphs;
|
||||||
using Sandbox.Engine;
|
using Sandbox.Engine;
|
||||||
using Sandbox.Internal;
|
using Sandbox.Internal;
|
||||||
|
using Sandbox.Tasks;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace ActionGraphs;
|
namespace ActionGraphs;
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class LiveGamePackage
|
public class LiveGamePackage
|
||||||
{
|
{
|
||||||
[TestInitialize]
|
|
||||||
public void TestInitialize()
|
|
||||||
{
|
|
||||||
Project.Clear();
|
|
||||||
PackageManager.ResetForUnitTest();
|
|
||||||
AssetDownloadCache.Initialize( $"{Environment.CurrentDirectory}/.source2/package_manager_folder" );
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCleanup]
|
|
||||||
public void TestCleanup()
|
|
||||||
{
|
|
||||||
Game.NodeLibrary = null;
|
|
||||||
GlobalContext.Current.FileMount = null;
|
|
||||||
|
|
||||||
Game.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asserts that all the ActionGraphs referenced by a given scene in a downloaded
|
/// Asserts that all the ActionGraphs referenced by a given scene in a downloaded
|
||||||
/// package have no errors.
|
/// package have no errors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow( "fish.sauna", 76972L, "scenes/finland.scene", 140,
|
[DataRow( "fish.sauna", 76972L, "scenes/finland.scene", 14,
|
||||||
"d174cab5-7a05-476c-a545-4db2fd685032", // Prefab references game object from other scene
|
"d174cab5-7a05-476c-a545-4db2fd685032", // Prefab references game object from other scene
|
||||||
"e9ac7c29-ff9f-4c3c-8d9d-7228c4711248", // Inventory method changed parameter types
|
"e9ac7c29-ff9f-4c3c-8d9d-7228c4711248", // Inventory method changed parameter types
|
||||||
"462927b9-1f01-4ba8-9f6b-2e1e6a5934e4" // Inventory method changed parameter types
|
"462927b9-1f01-4ba8-9f6b-2e1e6a5934e4" // Inventory method changed parameter types
|
||||||
)]
|
)]
|
||||||
public async Task AssertNoGraphErrorsInScene( string packageName, long? version, string scenePath, int graphCount, params string[] ignoreGuids )
|
public void AssertNoGraphErrorsInScene( string packageName, long? version, string scenePath, int graphCount, params string[] ignoreGuids )
|
||||||
{
|
{
|
||||||
|
AssetDownloadCache.Initialize( $"{Environment.CurrentDirectory}/.source2/package_manager_folder" );
|
||||||
|
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
// Let's make sure we have base content mounted
|
||||||
|
IGameInstanceDll.Current?.Bootstrap();
|
||||||
|
|
||||||
var ignoreGuidSet = new HashSet<Guid>( ignoreGuids.Select( Guid.Parse ) );
|
var ignoreGuidSet = new HashSet<Guid>( ignoreGuids.Select( Guid.Parse ) );
|
||||||
|
|
||||||
using ( var packageLoader = new Sandbox.PackageLoader( "Test", GetType().Assembly ) )
|
var packageIdent = version is { } v ? $"{packageName}#{v}" : packageName;
|
||||||
|
|
||||||
|
// Use the production loading logic - run blocking to ensure it completes
|
||||||
|
var loadTask = GameInstanceDll.Current.LoadGamePackageAsync( packageIdent, GameLoadingFlags.Host, CancellationToken.None );
|
||||||
|
SyncContext.RunBlocking( loadTask );
|
||||||
|
|
||||||
|
Assert.IsNotNull( GameInstanceDll.gameInstance, "Game instance should be loaded" );
|
||||||
|
Assert.AreNotEqual( 0, PackageManager.MountedFileSystem.FileCount, "We have package files mounted" );
|
||||||
|
Assert.AreNotEqual( 0, GlobalGameNamespace.TypeLibrary.Types.Count, "Library has classes" );
|
||||||
|
|
||||||
|
var sceneFile = ResourceLibrary.Get<SceneFile>( scenePath );
|
||||||
|
Assert.IsNotNull( sceneFile, "Target scene exists" );
|
||||||
|
|
||||||
|
ActionGraphDebugger.Enabled = true;
|
||||||
|
|
||||||
|
Game.ActiveScene = new Scene();
|
||||||
|
Game.ActiveScene.LoadFromFile( sceneFile.ResourcePath );
|
||||||
|
|
||||||
|
var graphs = ActionGraphDebugger.GetAllGraphs();
|
||||||
|
Assert.AreEqual( graphCount, graphs.Count, "Scene has expected graph count" );
|
||||||
|
|
||||||
|
var anyErrors = false;
|
||||||
|
|
||||||
|
foreach ( var graph in graphs )
|
||||||
{
|
{
|
||||||
using var enroller = packageLoader.CreateEnroller( "test-enroller" );
|
Console.WriteLine( $"{graph.Guid}: {graph.Title} {(ignoreGuidSet.Contains( graph.Guid ) ? "(IGNORED)" : "")}" );
|
||||||
|
|
||||||
GlobalContext.Current.FileMount = PackageManager.MountedFileSystem;
|
foreach ( var message in graph.Messages )
|
||||||
|
|
||||||
enroller.OnAssemblyAdded = ( a ) =>
|
|
||||||
{
|
{
|
||||||
Game.TypeLibrary.AddAssembly( a.Assembly, true );
|
Console.WriteLine( $" {message}" );
|
||||||
Game.NodeLibrary.AddAssembly( a.Assembly );
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "No package files mounted" );
|
|
||||||
|
|
||||||
var downloadOptions = new PackageLoadOptions
|
|
||||||
{
|
|
||||||
PackageIdent = version is { } v ? $"{packageName}#{v}" : packageName,
|
|
||||||
ContextTag = "client",
|
|
||||||
AllowLocalPackages = false
|
|
||||||
};
|
|
||||||
|
|
||||||
var activePackage = await PackageManager.InstallAsync( downloadOptions );
|
|
||||||
|
|
||||||
if ( version is not null )
|
|
||||||
{
|
|
||||||
Assert.AreEqual( version.Value, activePackage.Package.Revision.VersionId );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsNotNull( activePackage );
|
if ( !ignoreGuidSet.Contains( graph.Guid ) )
|
||||||
Assert.AreNotEqual( 0, PackageManager.MountedFileSystem.FileCount, "We have package files mounted" );
|
|
||||||
|
|
||||||
// Load the assemblies into the context
|
|
||||||
enroller.LoadPackage( packageName );
|
|
||||||
|
|
||||||
Assert.AreNotEqual( 0, GlobalGameNamespace.TypeLibrary.Types.Count, "Library has classes" );
|
|
||||||
|
|
||||||
JsonUpgrader.UpdateUpgraders( GlobalGameNamespace.TypeLibrary );
|
|
||||||
|
|
||||||
ResourceLoader.LoadAllGameResource( PackageManager.MountedFileSystem );
|
|
||||||
|
|
||||||
var sceneFile = ResourceLibrary.Get<SceneFile>( scenePath );
|
|
||||||
|
|
||||||
Assert.IsNotNull( sceneFile, "Target scene exists" );
|
|
||||||
|
|
||||||
ActionGraphDebugger.Enabled = true;
|
|
||||||
|
|
||||||
var anyErrors = false;
|
|
||||||
|
|
||||||
Game.ActiveScene = new Scene();
|
|
||||||
Game.ActiveScene.LoadFromFile( sceneFile.ResourcePath );
|
|
||||||
|
|
||||||
var graphs = ActionGraphDebugger.GetAllGraphs();
|
|
||||||
|
|
||||||
Assert.AreEqual( graphCount, graphs.Count, "Scene has expected graph count" );
|
|
||||||
|
|
||||||
foreach ( var graph in graphs )
|
|
||||||
{
|
{
|
||||||
Console.WriteLine( $"{graph.Guid}: {graph.Title} {(ignoreGuidSet.Contains( graph.Guid ) ? "(IGNORED)" : "")}" );
|
anyErrors |= graph.HasErrors();
|
||||||
|
|
||||||
foreach ( var message in graph.Messages )
|
|
||||||
{
|
|
||||||
Console.WriteLine( $" {message}" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !ignoreGuidSet.Contains( graph.Guid ) )
|
|
||||||
{
|
|
||||||
|
|
||||||
anyErrors |= graph.HasErrors();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionGraphDebugger.Enabled = false;
|
|
||||||
|
|
||||||
PackageManager.UnmountTagged( "client" );
|
|
||||||
|
|
||||||
Assert.IsFalse( anyErrors );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "Unmounted everything" );
|
ActionGraphDebugger.Enabled = false;
|
||||||
|
|
||||||
|
Assert.IsFalse( anyErrors, "No unexpected graph errors" );
|
||||||
|
|
||||||
|
GameInstanceDll.Current?.CloseGame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
engine/Sandbox.Test/Engine/Shutdown.cs
Normal file
24
engine/Sandbox.Test/Engine/Shutdown.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Misc;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class Shutdown
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Single()
|
||||||
|
{
|
||||||
|
// We already initialized the app for testing, so we can directly shutdown
|
||||||
|
TestInit.TestAppSystem.Shutdown();
|
||||||
|
|
||||||
|
// We need to re-init because other tests still need it
|
||||||
|
TestInit.TestAppSystem.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Multiple()
|
||||||
|
{
|
||||||
|
TestInit.TestAppSystem.Shutdown();
|
||||||
|
TestInit.TestAppSystem.Init();
|
||||||
|
TestInit.TestAppSystem.Shutdown();
|
||||||
|
TestInit.TestAppSystem.Init();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ public partial class FileSystem
|
|||||||
System.IO.Directory.CreateDirectory( ".source2/TestFolder" );
|
System.IO.Directory.CreateDirectory( ".source2/TestFolder" );
|
||||||
|
|
||||||
Sandbox.EngineFileSystem.Shutdown();
|
Sandbox.EngineFileSystem.Shutdown();
|
||||||
Sandbox.EngineFileSystem.Initialize( ".source2/TestFolder" );
|
Sandbox.EngineFileSystem.Initialize( ".source2/TestFolder", true );
|
||||||
|
|
||||||
Sandbox.EngineFileSystem.Root.WriteAllText( "root_text_file.txt", "Hello" );
|
Sandbox.EngineFileSystem.Root.WriteAllText( "root_text_file.txt", "Hello" );
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ namespace Packages;
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class PackageDownload
|
public class PackageDownload
|
||||||
{
|
{
|
||||||
[TestInitialize]
|
|
||||||
public void TestInitialize()
|
|
||||||
{
|
|
||||||
PackageManager.ResetForUnitTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow( "facepunch.sandbox" )]
|
[DataRow( "facepunch.sandbox" )]
|
||||||
[DataRow( "garry.grassworld" )]
|
[DataRow( "garry.grassworld" )]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ public partial class PackageLoader
|
|||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
Project.Clear();
|
Project.Clear();
|
||||||
PackageManager.ResetForUnitTest();
|
|
||||||
AssetDownloadCache.Initialize( $"{Environment.CurrentDirectory}/.source2/package_manager_folder" );
|
AssetDownloadCache.Initialize( $"{Environment.CurrentDirectory}/.source2/package_manager_folder" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ public partial class PackageLoader
|
|||||||
var addonName = "garry.grassworld";
|
var addonName = "garry.grassworld";
|
||||||
var addonClass = "GrassSpawner";
|
var addonClass = "GrassSpawner";
|
||||||
|
|
||||||
var (library, _, enroller) = Preamble();
|
var (library, packageLoader, enroller) = Preamble();
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "No package files mounted" );
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "No package files mounted" );
|
||||||
|
|
||||||
@@ -115,13 +114,16 @@ public partial class PackageLoader
|
|||||||
|
|
||||||
PackageManager.UnmountTagged( "client" );
|
PackageManager.UnmountTagged( "client" );
|
||||||
|
|
||||||
|
enroller.Dispose();
|
||||||
|
packageLoader.Dispose();
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "Unmounted everything" );
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "Unmounted everything" );
|
||||||
}
|
}
|
||||||
|
|
||||||
//[TestMethod]
|
//[TestMethod]
|
||||||
public async Task LoadPackageWithAddonWithLibrary( string packageName )
|
public async Task LoadPackageWithAddonWithLibrary( string packageName )
|
||||||
{
|
{
|
||||||
var (library, _, enroller) = Preamble();
|
var (library, packageLoader, enroller) = Preamble();
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "No package files mounted" );
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "No package files mounted" );
|
||||||
|
|
||||||
@@ -138,6 +140,9 @@ public partial class PackageLoader
|
|||||||
|
|
||||||
PackageManager.UnmountTagged( "client" );
|
PackageManager.UnmountTagged( "client" );
|
||||||
|
|
||||||
|
enroller.Dispose();
|
||||||
|
packageLoader.Dispose();
|
||||||
|
|
||||||
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "Unmounted everything" );
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount, "Unmounted everything" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +155,7 @@ public partial class PackageLoader
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task LoadRuntimeGamePackage()
|
public async Task LoadRuntimeGamePackage()
|
||||||
{
|
{
|
||||||
var (library, _, enroller) = Preamble();
|
var (library, packageLoader, enroller) = Preamble();
|
||||||
|
|
||||||
Project.AddFromFileBuiltIn( "addons/base" );
|
Project.AddFromFileBuiltIn( "addons/base" );
|
||||||
var project = Project.AddFromFile( "unittest/addons/spacewars" );
|
var project = Project.AddFromFile( "unittest/addons/spacewars" );
|
||||||
@@ -170,8 +175,11 @@ public partial class PackageLoader
|
|||||||
var gameClass = library.GetType( "SpaceWarsGameManager" );
|
var gameClass = library.GetType( "SpaceWarsGameManager" );
|
||||||
Assert.IsNotNull( gameClass, "Found game class" );
|
Assert.IsNotNull( gameClass, "Found game class" );
|
||||||
|
|
||||||
// cleanup
|
enroller.Dispose();
|
||||||
PackageManager.UnmountTagged( "client" );
|
packageLoader.Dispose();
|
||||||
|
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount );
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -203,6 +211,12 @@ public partial class PackageLoader
|
|||||||
Assert.IsInstanceOfType<InvalidOperationException>( exception );
|
Assert.IsInstanceOfType<InvalidOperationException>( exception );
|
||||||
|
|
||||||
Assert.IsTrue( exception.Message.Contains( "Disabled during static constructors." ) );
|
Assert.IsTrue( exception.Message.Contains( "Disabled during static constructors." ) );
|
||||||
|
|
||||||
|
enroller.Dispose();
|
||||||
|
packageLoader.Dispose();
|
||||||
|
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount );
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -328,10 +342,10 @@ public partial class PackageLoader
|
|||||||
|
|
||||||
//Assert.IsTrue( Project.CompileAsync )
|
//Assert.IsTrue( Project.CompileAsync )
|
||||||
|
|
||||||
// cleanup
|
|
||||||
PackageManager.UnmountTagged( "client" );
|
|
||||||
|
|
||||||
packageLoader.Dispose();
|
packageLoader.Dispose();
|
||||||
|
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount );
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class PackageManagement
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
PackageManager.ResetForUnitTest();
|
PackageManager.UnmountAll();
|
||||||
|
|
||||||
var dir = $"{Environment.CurrentDirectory}/.source2/package_manager_folder";
|
var dir = $"{Environment.CurrentDirectory}/.source2/package_manager_folder";
|
||||||
|
|
||||||
@@ -33,12 +33,6 @@ public class PackageManagement
|
|||||||
AssetDownloadCache.Initialize( dir );
|
AssetDownloadCache.Initialize( dir );
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCleanup]
|
|
||||||
public void TestCleanup()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should throw an exception on invalid/missing package
|
/// Should throw an exception on invalid/missing package
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -179,9 +173,14 @@ public class PackageManagement
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task DownloadPackagesWithMatchingFiles()
|
public async Task DownloadPackagesWithMatchingFiles()
|
||||||
{
|
{
|
||||||
|
var pm = PackageManager.ActivePackages;
|
||||||
|
|
||||||
var a = PackageManager.InstallAsync( new PackageLoadOptions( "titanovsky.ufrts_archery2_3", "fff" ) );
|
var a = PackageManager.InstallAsync( new PackageLoadOptions( "titanovsky.ufrts_archery2_3", "fff" ) );
|
||||||
var b = PackageManager.InstallAsync( new PackageLoadOptions( "titanovsky.ufrts_crates2", "fff" ) );
|
var b = PackageManager.InstallAsync( new PackageLoadOptions( "titanovsky.ufrts_crates2", "fff" ) );
|
||||||
|
|
||||||
await Task.WhenAll( a, b );
|
await Task.WhenAll( a, b );
|
||||||
|
|
||||||
|
PackageManager.UnmountAll();
|
||||||
|
Assert.AreEqual( 0, PackageManager.MountedFileSystem.FileCount );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,21 +7,18 @@ global using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class TestInit
|
public class TestInit
|
||||||
{
|
{
|
||||||
|
public static Sandbox.AppSystem TestAppSystem;
|
||||||
|
|
||||||
[AssemblyInitialize]
|
[AssemblyInitialize]
|
||||||
public static void ClassInitialize( TestContext context )
|
public static void ClassInitialize( TestContext context )
|
||||||
{
|
{
|
||||||
#if LIVE_UNIT_TEST
|
TestAppSystem = new TestAppSystem();
|
||||||
Sandbox.Application.InitLiveUnitTest<TestInit>();
|
TestAppSystem.Init();
|
||||||
#else
|
|
||||||
Sandbox.Application.InitUnitTest<TestInit>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AssemblyCleanup]
|
[AssemblyCleanup]
|
||||||
public static void AssemblyCleanup()
|
public static void AssemblyCleanup()
|
||||||
{
|
{
|
||||||
Sandbox.Application.ShutdownUnitTest();
|
TestAppSystem.Shutdown();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<ProjectReference Include="..\Sandbox.Engine\Sandbox.Engine.csproj" />
|
<ProjectReference Include="..\Sandbox.Engine\Sandbox.Engine.csproj" />
|
||||||
<ProjectReference Include="..\Sandbox.Tools\Sandbox.Tools.csproj" />
|
<ProjectReference Include="..\Sandbox.Tools\Sandbox.Tools.csproj" />
|
||||||
<ProjectReference Include="..\Sandbox.Menu\Sandbox.Menu.csproj" />
|
<ProjectReference Include="..\Sandbox.Menu\Sandbox.Menu.csproj" />
|
||||||
|
<ProjectReference Include="..\Sandbox.AppSystem\Sandbox.AppSystem.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace GameObjects.Components;
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class ModelPhysicsTests
|
public class ModelPhysicsTests
|
||||||
{
|
{
|
||||||
private static readonly Model CitizenModel = Model.Load( "models/citizen/citizen.vmdl" );
|
private static Model CitizenModel => Model.Load( "models/citizen/citizen.vmdl" );
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ComponentCreation()
|
public void ComponentCreation()
|
||||||
|
|||||||
@@ -13,7 +13,28 @@ internal class BuildAddons( string name ) : Step( name )
|
|||||||
string rootDir = Directory.GetCurrentDirectory();
|
string rootDir = Directory.GetCurrentDirectory();
|
||||||
string gameDir = Path.Combine( rootDir, "game" );
|
string gameDir = Path.Combine( rootDir, "game" );
|
||||||
|
|
||||||
Log.Info( "Step 1: Building Addons" );
|
Log.Info( "Step 1: Generate solution" );
|
||||||
|
|
||||||
|
string sboxDevPath = Path.Combine( gameDir, "sbox-dev.exe" );
|
||||||
|
if ( !File.Exists( sboxDevPath ) )
|
||||||
|
{
|
||||||
|
Log.Error( $"Error: sbox-dev.exe not found at {sboxDevPath}" );
|
||||||
|
return ExitCode.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gameTestSuccess = Utility.RunProcess(
|
||||||
|
sboxDevPath,
|
||||||
|
"-generatesolution",
|
||||||
|
gameDir
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( !gameTestSuccess )
|
||||||
|
{
|
||||||
|
Log.Error( "Solution generation failed!" );
|
||||||
|
return ExitCode.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info( "Step 2: Building Addons" );
|
||||||
|
|
||||||
bool addonsSuccess = Utility.RunDotnetCommand(
|
bool addonsSuccess = Utility.RunDotnetCommand(
|
||||||
gameDir,
|
gameDir,
|
||||||
@@ -26,7 +47,7 @@ internal class BuildAddons( string name ) : Step( name )
|
|||||||
return ExitCode.Failure;
|
return ExitCode.Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info( "Step 2: Building Menu" );
|
Log.Info( "Step 3: Building Menu" );
|
||||||
|
|
||||||
string menuBuildPath = Path.Combine( gameDir, "bin", "managed", "MenuBuild.exe" );
|
string menuBuildPath = Path.Combine( gameDir, "bin", "managed", "MenuBuild.exe" );
|
||||||
if ( !File.Exists( menuBuildPath ) )
|
if ( !File.Exists( menuBuildPath ) )
|
||||||
|
|||||||
@@ -72,27 +72,6 @@ internal class Test( string name ) : Step( name )
|
|||||||
return ExitCode.Failure;
|
return ExitCode.Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Info( "Step 3: Testing Game" );
|
|
||||||
|
|
||||||
string sboxDevPath = Path.Combine( gameDir, "sbox-dev.exe" );
|
|
||||||
if ( !File.Exists( sboxDevPath ) )
|
|
||||||
{
|
|
||||||
Log.Error( $"Error: sbox-dev.exe not found at {sboxDevPath}" );
|
|
||||||
return ExitCode.Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool gameTestSuccess = Utility.RunProcess(
|
|
||||||
sboxDevPath,
|
|
||||||
"-test",
|
|
||||||
gameDir
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( !gameTestSuccess )
|
|
||||||
{
|
|
||||||
Log.Error( "Game tests failed!" );
|
|
||||||
return ExitCode.Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Info( "All tests completed successfully!" );
|
Log.Info( "All tests completed successfully!" );
|
||||||
return ExitCode.Success;
|
return ExitCode.Success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ public partial class MenuSystem : IMenuSystem
|
|||||||
|
|
||||||
Dev?.Delete();
|
Dev?.Delete();
|
||||||
Dev = null;
|
Dev = null;
|
||||||
|
|
||||||
|
// Null so GC can have it's way
|
||||||
|
Instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Package oldGamePackage;
|
Package oldGamePackage;
|
||||||
|
|||||||
Reference in New Issue
Block a user