using Sandbox.Resources;
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Editor;
unsafe class ResourceCompileContextImp : ResourceCompileContext, IDisposable
{
IResourceCompilerContext _context;
public ResourceCompileContextImp( IResourceCompilerContext context )
{
_context = context;
Data = new DataStreamImp { _stream = _context.Data() };
StreamingData = new DataStreamImp { _stream = _context.StreamingData() };
}
public void Dispose()
{
_context = default;
if ( Data is DataStreamImp a ) a.Dispose();
if ( StreamingData is DataStreamImp b ) b.Dispose();
Data = default;
StreamingData = default;
}
public override string AbsolutePath => _context.FullPath();
public override string RelativePath => _context.RelativePath();
int _resourceVersion;
///
/// The resource version can be important
///
public override int ResourceVersion
{
get => _resourceVersion;
set
{
_resourceVersion = value;
_context.SpecifyResourceVersion( value );
}
}
internal override int WriteBlock( string blockName, IntPtr data, int count )
{
return _context.WriteBlock( blockName, data, count );
}
public override void AddRuntimeReference( string path )
{
_context.RegisterReference( path );
}
///
/// Add a reference that is needed to compile this resource, but isn't actually needed once compiled.
///
public override void AddCompileReference( string path )
{
_context.RegisterInputFileDependency( path, 0 );
}
///
/// Create a child resource
///
public override Child CreateChild( string path )
{
var childContext = _context.CreateChildContext( path );
return new ChildImplementation( childContext );
}
///
/// Read the source, either from in memory, or from disk
///
public override byte[] ReadSource()
{
// read from in memory, if we have an override
var buffer = _context.GetOverrideData();
if ( buffer.IsValid )
{
IntPtr basePtr = buffer.Base();
int length = buffer.TellMaxPut();
byte[] data = new byte[length];
System.Runtime.InteropServices.Marshal.Copy( basePtr, data, 0, length );
return data;
}
for ( int i = 0; i < 10; i++ )
{
try
{
var filename = AbsolutePath;
return System.IO.File.ReadAllBytes( filename );
}
catch ( System.IO.DirectoryNotFoundException )
{
Log.Warning( $"Couldn't find source file: [{AbsolutePath}] (directory not found)" );
return null;
}
catch ( System.IO.FileNotFoundException )
{
Log.Warning( $"Couldn't find source file: [{AbsolutePath}] (file not found)" );
return null;
}
catch { }
// retry
System.Threading.Thread.Sleep( 10 );
}
Log.Warning( $"Couldn't read source file: [{AbsolutePath}]" );
return null;
}
///
/// Load the json and scan it for paths or any embedded resources.
/// Returns modified Json, which your compiler should use instead.
///
public override string ScanJson( string jsonString )
{
var docOptions = new JsonDocumentOptions();
docOptions.MaxDepth = 512;
docOptions.CommentHandling = JsonCommentHandling.Skip;
var serializeOptions = new JsonSerializerOptions( JsonSerializerOptions.Default );
serializeOptions.MaxDepth = 512;
JsonNode node;
try
{
node = JsonNode.Parse( jsonString, default, docOptions );
}
catch ( System.Exception e )
{
Log.Warning( e, $"Couldn't s json [{AbsolutePath}]" );
return jsonString;
}
RecurseJsonNodes( node );
return node.ToJsonString( serializeOptions );
}
private void RecurseJsonNodes( JsonNode element )
{
if ( element is null )
return;
if ( element is JsonArray array )
{
// This is a copy cause it's modified during enumuration, specifically for embedded resources below
foreach ( var e in array.ToList() )
{
RecurseJsonNodes( e );
}
}
if ( element is JsonObject jsonobj )
{
foreach ( var e in jsonobj )
{
//
// An object with a $compiler has been found.
// This is an embedded resource definition.
// Process it via ResourceCompiler.
//
if ( e.Key == "$compiler" )
{
try
{
EmbeddedResource serialized = JsonSerializer.Deserialize( jsonobj );
if ( string.IsNullOrWhiteSpace( serialized.ResourceCompiler ) ) continue;
var allCompilers = EditorTypeLibrary.GetTypes();
var compilerType = allCompilers
.Where( x => x.Attributes
.OfType()
.Any( y => y.Name == serialized.ResourceCompiler ) )
.FirstOrDefault();
if ( compilerType is null ) continue;
var compiler = compilerType.Create();
if ( compiler is not null )
{
compiler.SetContext( this );
if ( compiler.CompileEmbeddedInternal( ref serialized ) )
{
jsonobj.ReplaceWith( JsonSerializer.SerializeToElement( serialized ) );
}
}
}
catch ( System.Exception exception )
{
Log.Warning( exception, "Couldn't deserialize embedded resource {}" );
}
}
RecurseJsonNodes( e.Value );
}
}
//
// If this is a string, it might be a filename
//
if ( element.GetValueKind() == JsonValueKind.String )
{
var str = element.ToString()?.Trim( '"' );
if ( string.IsNullOrWhiteSpace( str ) )
return;
var extension = System.IO.Path.GetExtension( str );
if ( string.IsNullOrWhiteSpace( extension ) )
return;
if ( AssetSystem.FindByPath( str ) is { } asset )
{
// If the file type doesn't compile to a file (like images don't) then
// we should just add it as a compile reference.
// If we use AddRuntimeReference it's going to try to load it when this
// resource loads. But if it's an image etc, it won'#t be able to, you'll end
// up with loads of Error loading resource file "textures/sprites/zombie_elite/zombie_elite_00.jpg_c"
if ( asset.AssetType == AssetType.ImageFile )
{
AddCompileReference( str );
Log.Trace( $"AddCompileReference: {str} {asset.Path}" );
}
else
{
AddRuntimeReference( asset.Path );
Log.Trace( $"AddRuntimeReference: {str} {asset.Path}" );
}
}
else if ( AssetType.FromExtension( extension ) is { } type )
{
// the c++ asset system supports refs to currently unrecognized assets where, if/when that asset is registered,
// our dependency tree will be rebuilt, and include proper refs to those assets - so lets make sure we use that, otherwise this is all order dependent
// (we're really just guessing this is an asset path, is there a better way to do this?)
AddRuntimeReference( str );
Log.Trace( $"RegisterUnrecognizedReference: {str} (Type: {type.FriendlyName})" );
}
}
}
class ChildImplementation : ResourceCompileContext.Child
{
private IResourceCompilerContextChild _context;
public ChildImplementation( IResourceCompilerContextChild childContext )
{
_context = childContext;
}
public override bool Compile()
{
return _context.CompileImmediately();
}
public override void SetInputData( string data )
{
_context.SetOverrideInputData( data );
}
}
public class DataStreamImp : DataStream
{
internal CResourceStream _stream;
public void Dispose()
{
_stream = default;
}
///
/// Write data to the resource
///
public override void Write( byte[] bytes )
{
fixed ( void* ptr = bytes )
{
_stream.WriteBytes( (IntPtr)ptr, bytes.Length );
}
}
//public override void SetAlignment( int bytes )
//{
// _stream.Align( bytes, 0 );
//}
}
}