Files
sbox-public/engine/Sandbox.Engine/Systems/SceneSystem/SceneMap.cs
Lorenz Junglas 94bb6f886b Shutdown scene early (#3576)
* Destroy ActiveScene early during game shutdown
* Don't call into native when deleting scenemaps
2025-12-11 09:10:03 +01:00

224 lines
4.9 KiB
C#

using NativeEngine;
using System.Threading;
namespace Sandbox;
/// <summary>
/// Map geometry that can be rendered within a <see cref="SceneWorld"/>.
/// </summary>
public sealed partial class SceneMap : IValid
{
/// <summary>
/// The scene world this map belongs to.
/// </summary>
public SceneWorld World { get; internal set; }
private IWorldReference WorldRef;
internal string WorldGroup { get; private set; }
internal IPVS PVS { get; private set; }
/// <summary>
/// Is the map valid.
/// </summary>
public bool IsValid => WorldRef.IsValid;
/// <summary>
/// Bounds of the map.
/// </summary>
public BBox Bounds
{
get
{
if ( !IsValid ) return default;
WorldRef.GetWorldBounds( out var min, out var max );
return new BBox( min, max );
}
}
public Vector3 WorldOrigin
{
set
{
if ( !IsValid )
return;
WorldRef.SetWorldTransform( new Transform( value ) );
}
}
/// <summary>
/// cs_assault
/// </summary>
public string MapName { get; private set; }
/// <summary>
/// maps/davej/cs_assault
/// </summary>
public string MapFolder { get; private set; }
private SceneMap()
{
}
/// <summary>
/// Create a scene map within a scene world.
/// </summary>
public SceneMap( SceneWorld sceneWorld, string map ) : this( sceneWorld, map, null )
{
}
/// <summary>
/// Create a scene map within a scene world.
/// </summary>
internal SceneMap( SceneWorld sceneWorld, string map, MapLoader loader )
{
if ( !CreateWorld( sceneWorld, map, false, loader is null ? 0 : loader.WorldOrigin ) )
return;
CreateEntities( "default_ents", loader );
sceneWorld.UpdateObjectsForRendering();
}
/// <summary>
/// Invoked when a map file is updated (re-compiled in Hammer.)
/// </summary>
internal static Action<string> OnMapUpdated;
private void CreateEntities( string map, MapLoader loader )
{
loader ??= new SceneMapLoader( World, null );
loader.CreateEntities( WorldRef, map );
}
private bool CreateWorld( SceneWorld sceneWorld, string map, bool async, Vector3 origin )
{
Assert.IsValid( sceneWorld );
MapFolder = System.IO.Path.ChangeExtension( map, null );
// CWorldRendererMgr::GetLocalMapName just strips maps/ from the start and uses that
map = System.IO.Path.ChangeExtension( map, null );
map = map.TrimStart( '\\', '/', ' ' );
if ( map.StartsWith( "maps/" ) ) map = map[5..];
if ( string.IsNullOrWhiteSpace( map ) )
return false;
MapName = map;
const bool loadVis = true;
const bool precacheOnly = false;
var worldGroup = sceneWorld.native.GetWorldDebugName();
var worldRef = g_pWorldRendererMgr.CreateWorld(
MapFolder + ".vpk",
sceneWorld,
async,
true,
loadVis,
precacheOnly,
worldGroup, // Worldgroup ID
new Transform( origin ) );
if ( !worldRef.IsValid )
{
Log.Warning( $"{this}: Unable to create world for map {map}" );
return false;
}
lock ( sceneWorld.InternalSceneMaps )
{
Assert.False( sceneWorld.InternalSceneMaps.Any( x => x.WorldRef == worldRef ), "Scene world already contains this scene map" );
}
WorldRef = worldRef;
MapFolder = worldRef.GetFolder();
WorldGroup = worldGroup;
World = sceneWorld;
if ( !async )
{
// We're not loading async so the world should be loaded now
OnWorldLoaded();
}
return true;
}
private void OnWorldLoaded()
{
WorldRef.PrecacheAllWorldNodes( 0x0080 );
PVS = g_pEnginePVSManager.BuildPvs( WorldRef );
World.AddSceneMap( this );
}
/// <summary>
/// Create scene map asynchronously for when large maps take time to load.
/// </summary>
public static Task<SceneMap> CreateAsync( SceneWorld sceneWorld, string map, CancellationToken cancelToken = default )
{
return CreateAsync( sceneWorld, map, null, cancelToken );
}
/// <summary>
/// Create scene map asynchronously for when large maps take time to load.
/// </summary>
internal static async Task<SceneMap> CreateAsync( SceneWorld sceneWorld, string map, MapLoader loader, CancellationToken cancelToken = default )
{
var sceneMap = new SceneMap();
if ( !sceneMap.CreateWorld( sceneWorld, map, true, loader.WorldOrigin ) )
return null;
if ( !sceneMap.IsValid )
return null;
var worldRef = sceneMap.WorldRef;
while ( !worldRef.IsWorldLoaded() )
{
await Task.Delay( 1, cancelToken );
cancelToken.ThrowIfCancellationRequested();
}
sceneMap.OnWorldLoaded();
sceneMap.CreateEntities( "default_ents", loader );
return sceneMap;
}
/// <summary>
/// Delete this scene map. You shouldn't access it anymore.
/// </summary>
public void Delete()
{
if ( World.IsValid() )
{
World.RemoveSceneMap( this );
//World.Delete(); // don#t delete this are you crazy
World = null;
}
if ( WorldRef.IsValid )
{
WorldRef.Release();
WorldRef = IntPtr.Zero;
}
if ( PVS.IsValid )
{
// don't destroy if another SceneWorld is using it
if ( !SceneWorld.All.Any( x => x.ActivePVS == PVS ) )
{
g_pEnginePVSManager.DestroyPvs( PVS );
}
PVS = default;
}
WorldGroup = null;
}
}