///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // D E R E L I C T // /// // (c) 2003..2024 using Godot; //using Microsoft.CodeAnalysis.CSharp.Syntax; using Godot.Collections; using gen = System.Collections.Generic; using Godot.Sharp.Extras; using System; using IO = System.IO; using System.Diagnostics; using Godot.NativeInterop; using System.Reflection; using gb = Godot.Bridge; using System.Runtime.InteropServices.ObjectiveC; using System.Runtime.CompilerServices; using System.Xml; using System.Buffers; using System.Collections.Immutable; namespace time { public record struct AbsUSec { static public AbsUSec Now => new AbsUSec( Time.GetTicksUsec() ); ulong _usec; public ulong Abs => _usec; public double Seconds => (double)_usec / 1_000_000.0; #region Cons public AbsUSec( ulong usec ) { _usec = usec; } public AbsUSec( long usec ) { Debug.Assert( usec > 0 ); _usec = (ulong)usec; } #endregion static public AbsUSec operator +( AbsUSec left, RelUSec right ) => new AbsUSec( (long)left.Abs + right.Rel ); static public RelUSec operator -( AbsUSec left, AbsUSec right ) => new RelUSec( (long)left.Abs - (long)right.Abs ); } public record struct RelUSec { long _usec; public long Rel => _usec; public double Seconds => (double)_usec / 1_000_000.0; public RelUSec( long usec ) { _usec = usec; } static public RelUSec operator -( RelUSec left, RelUSec right ) => new RelUSec( (long)left.Rel - (long)right.Rel ); } } public record struct TargetInfo { public readonly time.AbsUSec LastSeen = time.AbsUSec.Now; public readonly CollisionInfo3 Info; public float HowOldSec => (float)( ( time.AbsUSec.Now - LastSeen ).Rel ) / 1_000_000.0f; public TargetInfo( CollisionInfo3 info ) { Info = info; } } static public class Util { static public ImmutableList FindAll( this Node root ) where T : Node { log.debug( $"FindAll<{typeof( T )}>: {root.GetPath()}" ); var children = root.GetNodesOfType(); foreach( var ch in children ) { if( ch == null ) { log.warn( $"Found null in list" ); } } var immList = children.ToImmutableList(); var allChildren = root.GetChildren(); foreach( var child in allChildren ) { var childList = child.FindAll(); immList = immList.AddRange( childList ); } return immList; } static public bool VerifyNotNull( this Node node, bool logInfo = true, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = 0, [CallerArgumentExpression("node")] string dbgExp_node = "" ) { var nodeIsNotNull = node != null; var loc = new SourceLoc( "", dbgName, dbgPath, dbgLine ); if( logInfo && !nodeIsNotNull ) { log.warn( $"{loc.Log}: {dbgExp_node} IS NULL" ); } else { log.info( $"{loc.Log}: {dbgExp_node} is not null" ); } return nodeIsNotNull; } #region Math static public string Debug( this Vector3 vec ) => $"({vec.X:0.00}, {vec.X:0.00}, {vec.X:0.00})"; static public string DebugX( this Vector3 vec ) => $"({vec.X:0.00}, {vec.X:0.00})"; #endregion #region Strings static public string SafeOr( this string str, string def ) => !string.IsNullOrWhiteSpace( str ) ? str : def; static public bool NOW( this string str ) => string.IsNullOrWhiteSpace( str ); static public bool TryGetUserArgValue( this string[] strings, string whatArg, out string value ) { var index = 0; foreach( var arg in strings ) { if( arg == whatArg && index + 1 < strings.Length ) { value = strings[index + 1]; return true; } ++index; } value = ""; return false; } static public bool UserArgExists( this string[] strings, string whatArg ) => strings.Contains( whatArg ); static public string RelTime( this DateTime dt ) { var span = DateTime.Now - dt; if( span.TotalMilliseconds < 0 ) return $"Never"; if( span.TotalMilliseconds < 1000 ) return $"{(int)span.TotalMilliseconds}ms"; if( span.TotalMilliseconds < 100 * 1000 ) return $"{(int)( span.TotalMilliseconds / 1000.0 )}sec"; //If Under a year if( span.TotalMilliseconds < ( 52.0 * 168.0 * 60.0 * 60.0 * 1000.0 ) ) return $"{(int)( span.TotalMilliseconds / 60 * 1000.0 )}min"; return $"Always"; } #endregion #region Res static string ResProxySer( object obj ) { if( obj is Resource res ) { log.info( $"Res path from {res.ResourcePath}" ); return res.ResourcePath; } return ""; } static object ResProxyDes( string type, string str ) { log.info( $"Loading res {type} from {str}" ); var res = ResourceLoader.Load( str, type ); return res; } static ser.TypeProxy ResourceProxy = new( ResProxySer, ResProxyDes ); static gen.Dictionary TypeProxy = new() { { typeof(Resource), ResourceProxy }, }; static ser.XmlCfg XmlCfg = new() { Verbose = true, //Were not saving abitrary graphs Structure = ser.Datastructure.Tree, //FOr Godot types we cant otherwise save Proxies = TypeProxy.ToImmutableDictionary(), //For loading and saving Godot subclasses, we default to doing nothing TypesDefault = ser.Types.None, }; #endregion #region SaveLoad static string s_saveDir = "save"; static string s_saveExt = "xml"; static public void SetupSaveState( string saveDir = "save", string saveExt = "xml" ) { log.info( $"Setting up save state in {saveDir} with ext {saveExt}" ); s_saveDir = saveDir; s_saveExt = saveExt; lib.Util.checkAndAddDirectory( $"{s_saveDir}" ); } static public void SaveState( this Control control ) { var controlName = control.Name; var filename = $"{s_saveDir}/{controlName}.{s_saveExt}"; try { ser.XmlSer xml = new( XmlCfg ); if( IO.File.Exists( filename ) ) { // @@ TODO Backup the existing saves } using var stream = new IO.FileStream( filename, IO.FileMode.Create ); xml.Serialize( stream, control ); } catch( Exception ex ) { log.error( $"Ex {ex.Message} trying to save control {control.Name} into {filename}" ); } } static public void LoadState( this T control, bool verbose = false ) where T : Control { var controlName = control.Name; var filename = $"{s_saveDir}/{controlName}.{s_saveExt}"; try { ser.XmlSer xml = new( XmlCfg ); if( !IO.File.Exists( filename ) ) { return; } using var stream = new IO.FileStream( filename, IO.FileMode.Open ); xml.DeserializeInto( stream, control ); } catch( Exception ex ) { log.error( $"Ex {ex.Message} trying to save control {control.Name} into {filename}" ); } } #endregion static public Godot.Variant.Type CSTypeCodeToGodotVariantType( TypeCode tc ) { switch( tc ) { case TypeCode.Empty: return Variant.Type.Nil; case TypeCode.Boolean: return Variant.Type.Bool; case TypeCode.DateTime: return Variant.Type.Nil; case TypeCode.DBNull: return Variant.Type.Nil; case TypeCode.Decimal: case TypeCode.Single: case TypeCode.Double: return Variant.Type.Float; case TypeCode.Char: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: return Variant.Type.Int; case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return Variant.Type.Int; case TypeCode.Object: return Variant.Type.Object; case TypeCode.String: return Variant.Type.String; } return Variant.Type.Nil; } static public Variant.Type ConvertMarshalTypeToVariantType( Type type ) { if( type == typeof( Boolean ) ) return Variant.Type.Bool; if( type == typeof( Char ) ) return Variant.Type.Int; if( type == typeof( SByte ) ) return Variant.Type.Int; if( type == typeof( Int16 ) ) return Variant.Type.Int; if( type == typeof( Int32 ) ) return Variant.Type.Int; if( type == typeof( Int64 ) ) return Variant.Type.Int; if( type == typeof( Byte ) ) return Variant.Type.Int; if( type == typeof( UInt16 ) ) return Variant.Type.Int; if( type == typeof( UInt32 ) ) return Variant.Type.Int; if( type == typeof( UInt64 ) ) return Variant.Type.Int; if( type == typeof( Single ) ) return Variant.Type.Float; if( type == typeof( Double ) ) return Variant.Type.Float; if( type == typeof( String ) ) return Variant.Type.String; if( type == typeof( Vector2 ) ) return Variant.Type.Vector2; if( type == typeof( Vector2I ) ) return Variant.Type.Vector2I; if( type == typeof( Rect2 ) ) return Variant.Type.Rect2; if( type == typeof( Rect2I ) ) return Variant.Type.Rect2I; if( type == typeof( Transform2D ) ) return Variant.Type.Transform2D; if( type == typeof( Vector3 ) ) return Variant.Type.Vector3; if( type == typeof( Vector3I ) ) return Variant.Type.Vector3I; if( type == typeof( Basis ) ) return Variant.Type.Basis; if( type == typeof( Quaternion ) ) return Variant.Type.Quaternion; if( type == typeof( Transform3D ) ) return Variant.Type.Transform3D; if( type == typeof( Vector4 ) ) return Variant.Type.Vector4; if( type == typeof( Vector4I ) ) return Variant.Type.Vector4I; if( type == typeof( Projection ) ) return Variant.Type.Projection; if( type == typeof( Aabb ) ) return Variant.Type.Aabb; if( type == typeof( Color ) ) return Variant.Type.Color; if( type == typeof( Plane ) ) return Variant.Type.Plane; if( type == typeof( Callable ) ) return Variant.Type.Callable; if( type == typeof( Signal ) ) return Variant.Type.Signal; if( type == typeof( Enum ) ) return Variant.Type.Int; if( type == typeof( StringName ) ) return Variant.Type.StringName; if( type == typeof( NodePath ) ) return Variant.Type.NodePath; if( type == typeof( Rid ) ) return Variant.Type.Rid; if( type == typeof( Variant ) ) return Variant.Type.Nil; if( type == typeof( System.Collections.IEnumerable ) ) return Variant.Type.Array; /* if( type == typeof( GodotObjectOrDerivedArray ) ) return Variant.Type.Array; if( type == typeof( ByteArray ) ) return Variant.Type.PackedByteArray; if( type == typeof( Int32Array ) ) return Variant.Type.PackedInt32Array; if( type == typeof( Int64Array ) ) return Variant.Type.PackedInt64Array; if( type == typeof( Float32Array ) ) return Variant.Type.PackedFloat32Array; if( type == typeof( Float64Array ) ) return Variant.Type.PackedFloat64Array; if( type == typeof( StringArray ) ) return Variant.Type.PackedStringArray; if( type == typeof( Vector2Array ) ) return Variant.Type.PackedVector2Array; if( type == typeof( Vector3Array ) ) return Variant.Type.PackedVector3Array; if( type == typeof( ColorArray ) ) return Variant.Type.PackedColorArray; if( type == typeof( SystemArrayOfStringName ) ) return Variant.Type.Array; if( type == typeof( SystemArrayOfNodePath ) ) return Variant.Type.Array; if( type == typeof( SystemArrayOfRid ) ) return Variant.Type.Array; if( type == typeof( GodotObjectOrDerived ) ) return Variant.Type.Object; if( type == typeof( GodotDictionary ) ) return Variant.Type.Dictionary; if( type == typeof( GodotGenericDictionary ) ) return Variant.Type.Dictionary; if( type == typeof( GodotGenericArray ) ) return Variant.Type.Array; */ return Variant.Type.Nil; } static public Godot.Variant Convert( Type type, object obj ) { var tc = Type.GetTypeCode( type ); switch( tc ) { case TypeCode.Empty: return new Variant(); case TypeCode.Boolean: return Variant.From( (bool)obj ); case TypeCode.DateTime: return new Variant(); case TypeCode.DBNull: return new Variant(); case TypeCode.Decimal: case TypeCode.Single: case TypeCode.Double: return Variant.From( (double)obj ); case TypeCode.Char: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: return Variant.From( (int)obj ); case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return Variant.From( (int)obj ); case TypeCode.String: return Variant.From( (string)obj ); /* case TypeCode.Object: { // @@@ TODO Return proxy object //return Variant.From( obj ); //return new Variant(); if( obj is EntityId entId ) { return Variant.From( entId.id ); } } break; */ } return new Variant(); } static public Array ToGDArray<[MustBeVariant] T>( this gen.IEnumerable coll ) { var arr = new Array(); foreach( var t in coll ) { //GD.Print( $"Processing {coll.GetType()}" ); if( t == null ) { GD.PrintErr( $"ToGDArray<{typeof( T ).Name}> found a null object" ); continue; } arr.Add( t ); } return arr; } static public gen.IEnumerable Colliders( this ShapeCast3D sc ) { var count = sc.GetCollisionCount(); for( int i = 0; i < count; ++i ) { var ci = new CollisionInfo3() { position = sc.GetCollisionPoint( i ), normal = sc.GetCollisionNormal( i ), collider = sc.GetCollider( i ), rid = sc.GetColliderRid( i ), }; yield return ci; } } }