- Removed `UnicodeAt`
- Removed `EndsWith`
- Removed `LPad` and `RPad`
- Deprecated `BeginsWith` in favor of `string.StartsWith`
- Deprecated `LStrip` and `RStrip` in favor of `string.TrimStart` and `string.TrimEnd`
- Remove `VariantSpanDisposer`, no need to dispose of the Variant Spans
since we are now borrowing the Variants instead of copying them.
- Remove `VariantSpanExtensions.Cleared` that was only used so the
Span was initialized for `VariantSpanDisposer` to know what to dispose.
- Fix stackalloc Spans to use constant VarArgsSpanThreshold
and avoid bound checks.
- Replaced `MD5Buffer`, `MD5Text`, `SHA256Buffer` and `SHA256Text` implementation to use the `System.Security.Cryptography` classes and avoid marshaling.
- Added `SHA1Buffer` and `SHA1Text`.
- Renamed `ToUTF8` to `ToUTF8Buffer`.
- Renamed `ToAscii` to `ToASCIIBuffer`.
- Added `ToUTF16Buffer` and `ToUTF32Buffer`.
- Added `GetStringFromUTF16` and `GetStringFromUTF32`.
- Renamed `IsValidInteger` to `IsValidInt`.
- Added `IsValidFileName`.
- Added `IsValidHexNumber`.
- Added support for IPv6 to `IsValidIPAddress`.
- Added `ValidateNodeName`.
- Updated the documentation of the `IsValid*` methods.
- Moved `GetBaseName` to keep methods alphabetically sorted.
- Removed `Length`, users should just use the Length property.
- Removed `Insert`, string already has a method with the same signature that takes precedence.
- Removed `Erase`.
- Removed `ToLower` and `ToUpper`, string already has methods with the same signature that take precedence.
- Removed `FindLast` in favor of `RFind`.
- Replaced `RFind` and `RFindN` implemenation with a ca ll to `string.LastIndexOf` to avoid marshaling.
- Added `LPad` and `RPad`.
- Added `StripEscapes`.
- Replaced `LStrip` and `RStrip` implementation with a call to `string.TrimStart` and `string.TrimEnd`.
- Added `TrimPrefix` and `TrimSuffix`.
- Renamed `OrdAt` to `UnicodeAt`.
- Added `CountN` and move the `caseSensitive` parameter of `Count` to the end.
- Added `Indent` and `Dedent`.
These callbacks are used for marshaling by callables and generic Godot
collections.
C# generics don't support specialization the way C++ templates do.
I knew NativeAOT could optimize away many type checks when the types
are known at compile time, but I didn't trust the JIT would do as good
a job, so I initially went with cached function pointers.
Well, it turns out the JIT is also very good at optimizing in this
scenario, so I'm changing the methods to do the conversion directly,
rather than returning a function pointer for the conversion.
The methods were moved to `VariantUtils`, and were renamed from
`GetFromVariantCallback/GetToVariantCallback` to `ConvertTo/CreateFrom`.
The new implementation looks like it goes through many `if` checks
at runtime to find the right branch for the type, but in practice it
works pretty much like template specialization. The JIT only generates
code for the relevant branch. Together with inlining, the result is
very close or the same as doing the conversion manually:
```cs
godot_variant variant;
int foo = variant.Int;
int bar = VariantUtils.ConvertTo<int>(variant);
```
If the type is a generic Godot collection, the conversion still goes
through a function pointer call.
The new code happens to be much shorter as well, with the file going
from 1057 lines to 407.
Side note: `Variant.cs` was mistakenly created in the wrong folder,
so I moved it to the `Core` folder.
This allows using generic Godot collections as type arguments for other
generic Godot collections. This also allows generic Godot collections
as parameter or return type in dynamic Callable invocations.
We aim to make the C# API reflection-free, mainly for concerns about
performance, and to be able to target NativeAOT in refletion-free mode,
which reduces the binary size.
One of the main usages of reflection still left was the dynamic
invokation of callable delegates, and for some time I wasn't sure
I would find an alternative solution that I'd be happy with.
The new solution uses trampoline functions to invoke the delegates:
```
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
if (args.Count != 1)
throw new ArgumentException($"Callable expected 1 arguments but received {args.Count}.");
string res = ((Func<int, string>)delegateObj)(
VariantConversionCallbacks.GetToManagedCallback<int>()(args[0])
);
ret = VariantConversionCallbacks.GetToVariantCallback<string>()(res);
}
Callable.CreateWithUnsafeTrampoline((int num) => "Foo" + num, &Trampoline);
```
Of course, this is too much boilerplate for user code. To improve this,
the `Callable.From` methods were added. These are overloads that take
`Action` and `Func` delegates, which covers the most common use cases:
lambdas and method groups:
```
// Lambda
Callable.From((int num) => "Foo" + num);
// Method group
string AppendNum(int num) => "Foo" + num;
Callable.From(AppendNum);
```
Unfortunately, due to limitations in the C# language, implicit
conversions from delegates to `Callable` are not supported.
`Callable.From` does not support custom delegates. These should be
uncommon, but the Godot C# API actually uses them for event signals.
As such, the bindings generator was updated to generate trampoline
functions for event signals. It was also optimized to use `Action`
instead of a custom delegate for parameterless signals, which removes
the need for the trampoline functions for those signals.
The change to reflection-free invokation removes one of the last needs
for `ConvertVariantToManagedObjectOfType`. The only remaining usage is
from calling script constructors with parameters from the engine
(`CreateManagedForGodotObjectScriptInstance`). Once that one is made
reflection-free, `ConvertVariantToManagedObjectOfType` can be removed.
We use collectible AssemblyLoadContexts as that's the only way to allow
reloading assemblies after building. However, collectible assemblies
have some restrictions:
- https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/collectible-assemblies#restrictions-on-collectible-assemblies
Those restrictions can cause issues with third-party code, such as some
mocking libraries.
In order to work around this problem, we're going to load assemblies
as collectible only in Godot editor, and not when running games.
These issues will still exist in the editor, but this will be enough
for some users.
If the delegate target is an Object, the connected signal will be registered in that object instead of the middleman. So when that object is destroyed, the signal will be properly disconnected.
Scripts that are instantiated at some point will always be recreated
if they ever become placeholders to prevent non-tool scripts
instantiated manually by users to become placeholders, if they
do become placeholders due to errors that prevent instantiation
(such as a missing parameterless constructor) these scripts
will also be recreated replacing the temporary placeholder.
If a script is marked as a tool but becomes a non-tool script
in a rebuild, the script will become a placeholder and will
no longer be considered applicable to be replaced by an instance
since the user explicitly removed the Tool attribute.