Files
sbox-public/engine/Sandbox.Engine/Resources/ResourceLoader.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

160 lines
3.9 KiB
C#

using Sandbox.Engine;
using System.Diagnostics;
namespace Sandbox;
internal static class ResourceLoader
{
internal static void LoadAllGameResource( BaseFileSystem fileSystem )
{
var sw = Stopwatch.StartNew();
var types = Game.TypeLibrary.GetAttributes<AssetTypeAttribute>().DistinctBy( x => x.Extension )
.ToDictionary( x => $".{x.Extension}_c", x => x, StringComparer.OrdinalIgnoreCase );
var allFiles = fileSystem.FindFile( "/", "*", true ).ToArray();
var allResources = new List<GameResource>();
Clear();
foreach ( var file in allFiles )
{
var extension = System.IO.Path.GetExtension( file );
if ( !types.TryGetValue( extension, out var type ) )
continue;
try
{
var se = Game.Resources.LoadGameResource( type, file, fileSystem, true );
if ( se != null ) allResources.Add( se );
}
catch ( Exception ex )
{
Log.Warning( ex, $"Exception when trying to load {file}" );
}
}
//
// When we're loading a bunch of GameResource we defer their PostLoad until everything is loaded.
// Everyone is gonna wanna do Resource.Get<>() within their PostLoad and not care about load order.
// This keeps things intuitive for end users.
//
foreach ( var resource in allResources )
{
resource.PostLoadInternal();
}
foreach ( var type in types )
{
AddWatcherForType( type.Value );
}
// TODO: Check for edited but not saved OR recompiled assets and load in their values on server/client
// like editing an asset while the gamemode is running would?
}
static Dictionary<string, FileWatch> Watchers = new();
static void AddWatcherForType( AssetTypeAttribute type )
{
if ( Watchers.TryGetValue( type.Name, out var watcher ) )
{
watcher.Dispose();
}
watcher = EngineFileSystem.Mounted.Watch( $"*.{type.Extension}_c" );
watcher.OnChanges += ( w ) => OnAssetFilesChanged( w, type );
Watchers[type.Name] = watcher;
}
private static void OnAssetFilesChanged( FileWatch watch, AssetTypeAttribute type )
{
foreach ( var change in watch.Changes )
{
OnAssetFileChanged( change, type );
}
}
static void OnAssetFileChanged( string file, AssetTypeAttribute type )
{
var fs = EngineFileSystem.Mounted;
if ( !file.EndsWith( "_c" ) )
file += "_c";
//
// Asset doesn't exist, maybe just added?
//
if ( !ResourceLibrary.TryGet<GameResource>( file.Trim( '/' ), out var asset ) || asset.IsPromise )
{
// file wasn't found, so I don't know what was happening.
if ( !fs.FileExists( file ) )
return;
Log.Info( $"Detected Added File {file}" );
Game.Resources.LoadGameResource( type, file, fs );
return;
}
//
// File was removed, tell the asset system it died
//
if ( !fs.FileExists( file ) )
{
Log.Info( $"Detected Asset File Deleted {file}" );
// Removes from ResourceLibrary
asset.DestroyInternal();
return;
}
Span<byte> data = fs.ReadAllBytes( file );
if ( data.Length <= 3 )
{
Log.Warning( $"Couldn't load json data from {file}" );
return;
}
bool hasCompiledChanges = asset.TryLoadFromData( data );
bool externalChanges = false;
if ( hasCompiledChanges )
{
// check for source file changes
if ( fs.FileExists( asset.ResourcePath ) )
{
var jsonBlob = fs.ReadAllText( asset.ResourcePath );
if ( string.IsNullOrEmpty( jsonBlob ) ) return;
var sourceHash = jsonBlob.FastHash();
if ( sourceHash != asset.LastSavedSourceHash && asset.LastSavedSourceHash != 0 )
{
IToolsDll.Current?.RunEvent<ResourceLibrary.IEventListener>( i => i.OnExternalChanges( asset ) );
externalChanges = true;
}
}
}
asset.PostReloadInternal();
if ( externalChanges )
{
IToolsDll.Current?.RunEvent<ResourceLibrary.IEventListener>( i => i.OnExternalChangesPostLoad( asset ) );
}
}
internal static void Clear()
{
// Dispose of watchers too
foreach ( var watcher in Watchers ) watcher.Value.Dispose();
Watchers.Clear();
}
}