551 lines
13 KiB
C#
551 lines
13 KiB
C#
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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<T> FindAll<T>( this Node root ) where T : Node
|
|
{
|
|
log.debug( $"FindAll<{typeof( T )}>: {root.GetPath()}" );
|
|
|
|
var children = root.GetNodesOfType<T>();
|
|
|
|
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<T>();
|
|
|
|
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<Type, ser.TypeProxy> 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<T>( 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<T>( 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<T> ToGDArray<[MustBeVariant] T>( this gen.IEnumerable<T> coll )
|
|
{
|
|
var arr = new Array<T>();
|
|
|
|
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<CollisionInfo3> 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;
|
|
}
|
|
}
|
|
}
|