gd_core/addons/core/misc/Util.cs

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;
}
}
}