mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-10 07:18:28 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
340 lines
9.5 KiB
C#
340 lines
9.5 KiB
C#
using Sandbox.Engine;
|
|
using System;
|
|
using System.Text.Json.Nodes;
|
|
using System.Threading;
|
|
|
|
namespace Editor;
|
|
|
|
public static partial class AssetSystem
|
|
{
|
|
internal static CloudAssetDirectory CloudDirectory { get; set; }
|
|
|
|
/// <inheritdoc cref="InstallAsync(Package, bool, Action{float}, CancellationToken)"/>
|
|
public static async Task<Asset> InstallAsync( string packageIdent, bool skipIfInstalled = true, Action<float> loading = null, CancellationToken token = default )
|
|
{
|
|
loading?.Invoke( 0.0f );
|
|
var package = await Package.FetchAsync( packageIdent, false, false );
|
|
return await InstallAsync( package, skipIfInstalled, loading, token );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Install a cloud package. Will return the primary asset on completion (if it has one)
|
|
/// </summary>
|
|
/// <param name="package"></param>
|
|
/// <param name="skipIfInstalled">Skip downloading the remote package if any version of this package is already installed.</param>
|
|
/// <param name="loading"></param>
|
|
/// <param name="token"></param>
|
|
public static async Task<Asset> InstallAsync( Package package, bool skipIfInstalled = true, Action<float> loading = null, CancellationToken token = default )
|
|
{
|
|
if ( package == null )
|
|
return null;
|
|
|
|
if ( package.Revision == null )
|
|
return null;
|
|
|
|
if ( !CanCloudInstall( package ) )
|
|
return null;
|
|
|
|
if ( !skipIfInstalled || !IsCloudInstalled( package ) )
|
|
{
|
|
// download the manifest
|
|
await package.Revision.DownloadManifestAsync( token );
|
|
|
|
await DownloadCloudFiles( package, loading, token );
|
|
|
|
foreach ( var file in package.Revision.Manifest.Files )
|
|
{
|
|
var fullPath = FileSystem.Cloud.GetFullPath( file.Path );
|
|
var asset = RegisterFile( fullPath );
|
|
|
|
if ( asset is null )
|
|
continue;
|
|
|
|
asset.Package = package;
|
|
|
|
// update ref version on any assets that depend on this
|
|
ReplaceReferences( asset.GetDependants( false ), package, package );
|
|
}
|
|
|
|
CloudDirectory.AddPackage( package );
|
|
EditorEvent.Run( "package.changed.installed", package );
|
|
}
|
|
|
|
var primaryAssetName = package.PrimaryAsset;
|
|
return FindByPath( primaryAssetName );
|
|
}
|
|
|
|
internal static void UninstallPackage( Package package )
|
|
{
|
|
if ( !IsCloudInstalled( package ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
var contents = GetPackageFiles( package );
|
|
CloudDirectory.RemovePackage( package );
|
|
|
|
foreach ( var file in contents )
|
|
{
|
|
var fullPath = FileSystem.Cloud.GetFullPath( file );
|
|
var asset = FindByPath( fullPath );
|
|
|
|
if ( asset is null || asset.Package != package )
|
|
continue;
|
|
|
|
// some files are shared between packages, so check if any other packages need this file
|
|
var dependants = asset.GetDependants( false );
|
|
Package requiredBy = null;
|
|
foreach ( var dependant in dependants )
|
|
{
|
|
if ( dependant.Package is null ) continue;
|
|
|
|
if ( !IsCloudInstalled( dependant.Package ) )
|
|
continue;
|
|
|
|
requiredBy = dependant.Package;
|
|
break;
|
|
}
|
|
|
|
if ( requiredBy == null )
|
|
{
|
|
// no other packages need this, we can delete it
|
|
asset.Delete();
|
|
}
|
|
else
|
|
{
|
|
// update any assets that depend on this to point to the package that remains
|
|
ReplaceReferences( asset.GetDependants( false ), package, requiredBy );
|
|
|
|
asset.Package = requiredBy;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces any references in <paramref name="assets"/> to <paramref name="fromPackage"/> with a reference to <paramref name="toPackage"/>
|
|
/// (does a replace by ident to avoid having to open the map etc)
|
|
/// </summary>
|
|
private static void ReplaceReferences( IEnumerable<Asset> assets, Package fromPackage, Package toPackage )
|
|
{
|
|
if ( assets.Count() < 1 ) return;
|
|
|
|
string newIdent = toPackage?.GetIdent( false, true );
|
|
string projectPath = Project.Current.GetAssetsPath().Replace( '\\', '/' );
|
|
|
|
foreach ( var asset in assets )
|
|
{
|
|
// more bs
|
|
if ( !asset.AbsolutePath.StartsWith( projectPath, StringComparison.OrdinalIgnoreCase ) )
|
|
continue;
|
|
|
|
if ( !asset.AssetType.IsGameResource )
|
|
{
|
|
var config = asset?.Publishing?.ProjectConfig;
|
|
if ( config is null || config.EditorReferences is null )
|
|
continue;
|
|
|
|
int count = config.EditorReferences.RemoveAll( fromPackage.IsNamed );
|
|
|
|
if ( count > 0 )
|
|
{
|
|
if ( newIdent != null && !config.EditorReferences.Contains( newIdent ) )
|
|
config.EditorReferences.Add( newIdent );
|
|
|
|
config.EditorReferences = config.EditorReferences.OrderBy( x => x ).ToList();
|
|
asset.MetaData.Set( "publish", asset.Publishing );
|
|
}
|
|
}
|
|
else if ( typeof( GameResource ).IsAssignableFrom( asset.AssetType.ResourceType ) && asset.LoadResource() is GameResource gameResource )
|
|
{
|
|
string filename = asset.GetSourceFile( true );
|
|
if ( string.IsNullOrWhiteSpace( filename ) )
|
|
continue;
|
|
|
|
var json = System.IO.File.ReadAllText( filename );
|
|
|
|
if ( JsonNode.Parse( json ) is not JsonObject jso )
|
|
continue;
|
|
|
|
if ( jso["__references"] is not JsonArray references )
|
|
continue;
|
|
|
|
var newReferences = references.Select( x => x.ToString() ).ToList();
|
|
int count = newReferences.RemoveAll( fromPackage.IsNamed );
|
|
|
|
if ( count > 0 )
|
|
{
|
|
if ( newIdent != null && !newReferences.Contains( newIdent ) )
|
|
newReferences.Add( newIdent );
|
|
|
|
jso["__references"] = JsonValue.Create( newReferences.OrderBy( x => x ) );
|
|
|
|
gameResource.SaveToDisk( filename, jso.ToJsonString( Json.options ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the locally installed package revision by ident
|
|
/// </summary>
|
|
public static Package.IRevision GetInstalledRevision( string packageIdent )
|
|
{
|
|
return CloudDirectory.FindPackage( packageIdent )?.Revision;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this package installed in our cloud directory?
|
|
/// </summary>
|
|
public static bool IsCloudInstalled( string packageIdent )
|
|
{
|
|
return CloudDirectory.FindPackage( packageIdent ) != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is a version this package installed in our cloud directory?
|
|
/// </summary>
|
|
public static bool IsCloudInstalled( Package package, bool exactVersion = false )
|
|
{
|
|
if ( package is null ) return false;
|
|
if ( package.Org is null )
|
|
{
|
|
Log.Trace( $"{package.FullIdent} no org" );
|
|
return false;
|
|
}
|
|
if ( !package.IsRemote )
|
|
{
|
|
Log.Trace( $"{package.FullIdent} not remote" );
|
|
return false;
|
|
}
|
|
|
|
var local = CloudDirectory.FindPackage( package.FullIdent );
|
|
if ( local is null )
|
|
{
|
|
Log.Trace( $"{package.FullIdent} wasn't found in CloudDirectory" );
|
|
return false;
|
|
}
|
|
|
|
if ( exactVersion && package.Revision is not null && package.Revision.VersionId != local.Revision.VersionId )
|
|
{
|
|
Log.Trace( $"{package.Revision.VersionId} != {local.Revision.VersionId} version is different" );
|
|
return false; // version is different
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all packages in the download cache
|
|
/// </summary>
|
|
public static IReadOnlyCollection<Package> GetInstalledPackages()
|
|
{
|
|
return CloudDirectory.GetPackages();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all packages, referenced by assets in the current project, in the download cache
|
|
/// </summary>
|
|
public static IReadOnlyCollection<Package> GetReferencedPackages()
|
|
{
|
|
return CloudAsset.GetAssetReferences( true )
|
|
.Select( CloudDirectory.FindPackage )
|
|
.Where( p => p != null ).ToList();
|
|
}
|
|
|
|
public static IReadOnlyCollection<string> GetPackageFiles( Package package )
|
|
{
|
|
return CloudDirectory.GetPackageFiles( package ).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is this package type something we can install?
|
|
/// </summary>
|
|
public static bool CanCloudInstall( Package package )
|
|
{
|
|
if ( package.TypeName == "map" ) return false;
|
|
if ( package.TypeName == "collection" ) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the files from the
|
|
/// </summary>
|
|
private static async Task DownloadCloudFiles( Package package, Action<float> progress, CancellationToken token )
|
|
{
|
|
long totalSize = package.Revision.Manifest.Files.Sum( x => x.Size );
|
|
long downloaded = 0;
|
|
|
|
IToolsDll.Current?.RunEvent( "package.download.start", package, token );
|
|
|
|
//
|
|
// Process 16 files at a time
|
|
//
|
|
await package.Revision.Manifest.Files.ForEachTaskAsync( async ( e ) =>
|
|
{
|
|
await DownloadFile( package, e, token );
|
|
downloaded += e.Size;
|
|
|
|
float frac = (float)((double)downloaded / (double)totalSize);
|
|
progress?.Invoke( frac );
|
|
IToolsDll.Current?.RunEvent( "package.download.update", package, frac );
|
|
}, 16 );
|
|
|
|
IToolsDll.Current?.RunEvent( "package.download.complete", package );
|
|
}
|
|
|
|
static async Task DownloadFile( Package package, ManifestSchema.File entry, CancellationToken token )
|
|
{
|
|
ThreadSafe.AssertIsMainThread();
|
|
|
|
//
|
|
// Ignore thumbnails
|
|
//
|
|
string path = entry.Path.StartsWith( "/" ) ? entry.Path : $"/{entry.Path}";
|
|
if ( path == $"/{package.FullIdent}/thumb.png" )
|
|
return;
|
|
|
|
//
|
|
// Ignore assemblies
|
|
//
|
|
if ( entry.Path.EndsWith( ".dll" ) )
|
|
return;
|
|
|
|
//
|
|
// This file exists in core, no need to download it (at this moment)
|
|
//
|
|
if ( EngineFileSystem.CoreContent.FileExists( entry.Path ) )
|
|
return;
|
|
|
|
//
|
|
// File exists - we should probably check crcs and shit
|
|
//
|
|
if ( FileSystem.Cloud.FileExists( entry.Path ) && FileSystem.Cloud.FileSize( entry.Path ) == entry.Size )
|
|
return;
|
|
|
|
var targetPath = System.IO.Path.GetDirectoryName( entry.Path );
|
|
FileSystem.Cloud.CreateDirectory( targetPath );
|
|
|
|
var targetFile = FileSystem.Cloud.GetFullPath( entry.Path );
|
|
|
|
if ( System.IO.File.Exists( targetFile ) )
|
|
{
|
|
System.IO.File.Delete( targetFile );
|
|
}
|
|
|
|
CloudDirectory.AddFile( entry.Path, entry.Crc, entry.Size, package.FullIdent, package.Revision.VersionId );
|
|
|
|
if ( entry.Size == 0 )
|
|
{
|
|
await System.IO.File.WriteAllTextAsync( targetFile, "" );
|
|
}
|
|
else
|
|
{
|
|
var url = $"{entry.Url}";
|
|
await Sandbox.Utility.Web.DownloadFile( url, targetFile, token );
|
|
}
|
|
}
|
|
}
|
|
|