diff --git a/Intersect (Core)/Compression/GzipCompression.cs b/Framework/Intersect.Framework.Core/Compression/GzipCompression.cs
similarity index 92%
rename from Intersect (Core)/Compression/GzipCompression.cs
rename to Framework/Intersect.Framework.Core/Compression/GzipCompression.cs
index ac0c79725c..12873cd46c 100644
--- a/Intersect (Core)/Compression/GzipCompression.cs
+++ b/Framework/Intersect.Framework.Core/Compression/GzipCompression.cs
@@ -23,7 +23,7 @@ private static void CreateProvider()
// Take a few bytes out of this delicious morsel and grow stronk.
var keyBytes = ASCIIEncoding.ASCII.GetBytes(cryptoKey);
- cryptoProvider.Key = keyBytes.Take(16).ToArray();
+ cryptoProvider.Key = keyBytes.Take(16).ToArray();
cryptoProvider.IV = keyBytes.Reverse().Take(16).ToArray();
}
@@ -56,11 +56,11 @@ public static CryptoStream CreateDecompressedFileStream(string fileName)
}
///
- /// Read decompressed unencrypted data from an existing filestream.
+ /// Read decompressed unencrypted data from an existing .
///
- /// The Filestream to write data from.
+ /// The to write data from.
/// Returns a decompressed of the stream's content.
- public static CryptoStream CreateDecompressedFileStream(FileStream stream)
+ public static CryptoStream CreateDecompressedFileStream(Stream stream)
{
if (cryptoProvider == null)
{
diff --git a/Framework/Intersect.Framework.Core/Config/OptionsStrings.Designer.cs b/Framework/Intersect.Framework.Core/Config/OptionsStrings.Designer.cs
index 6d9382468b..af42dbc1ed 100644
--- a/Framework/Intersect.Framework.Core/Config/OptionsStrings.Designer.cs
+++ b/Framework/Intersect.Framework.Core/Config/OptionsStrings.Designer.cs
@@ -8,9 +8,6 @@
//------------------------------------------------------------------------------
namespace Intersect.Framework.Core.Config {
- using System;
-
-
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
diff --git a/Framework/Intersect.Framework.Core/Core/IApplicationContext.cs b/Framework/Intersect.Framework.Core/Core/IApplicationContext.cs
index b41d9f0abe..65db8d77c8 100644
--- a/Framework/Intersect.Framework.Core/Core/IApplicationContext.cs
+++ b/Framework/Intersect.Framework.Core/Core/IApplicationContext.cs
@@ -51,6 +51,11 @@ bool IsDebug
///
bool IsRunning { get; }
+ ///
+ /// The name of the application.
+ ///
+ string Name { get; }
+
///
/// The options the application was started with.
///
diff --git a/Intersect (Core)/Core/IApplicationContext`1.cs b/Framework/Intersect.Framework.Core/Core/IApplicationContext`1.cs
similarity index 100%
rename from Intersect (Core)/Core/IApplicationContext`1.cs
rename to Framework/Intersect.Framework.Core/Core/IApplicationContext`1.cs
diff --git a/Framework/Intersect.Framework.Core/GameObjects/DescriptorStrings.Designer.cs b/Framework/Intersect.Framework.Core/GameObjects/DescriptorStrings.Designer.cs
index 6e74f2a7d5..f3c9b6810d 100644
--- a/Framework/Intersect.Framework.Core/GameObjects/DescriptorStrings.Designer.cs
+++ b/Framework/Intersect.Framework.Core/GameObjects/DescriptorStrings.Designer.cs
@@ -8,9 +8,6 @@
//------------------------------------------------------------------------------
namespace Intersect.Framework.Core.GameObjects {
- using System;
-
-
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
diff --git a/Framework/Intersect.Framework.Core/Intersect.Framework.Core.csproj b/Framework/Intersect.Framework.Core/Intersect.Framework.Core.csproj
index 7b31eb89fd..d55a61d3be 100644
--- a/Framework/Intersect.Framework.Core/Intersect.Framework.Core.csproj
+++ b/Framework/Intersect.Framework.Core/Intersect.Framework.Core.csproj
@@ -28,6 +28,7 @@
+
diff --git a/Framework/Intersect.Framework.Core/Point.cs b/Framework/Intersect.Framework.Core/Point.cs
index ea38e38113..e28bf45ea2 100644
--- a/Framework/Intersect.Framework.Core/Point.cs
+++ b/Framework/Intersect.Framework.Core/Point.cs
@@ -76,4 +76,9 @@ public static Point FromString(string val)
public static Point operator /(Point point, float scalar) => new((int)(point.X / scalar), (int)(point.Y / scalar));
+ public void Deconstruct(out int x, out int y)
+ {
+ x = X;
+ y = Y;
+ }
}
diff --git a/Intersect (Core)/Utilities/Timing.cs b/Framework/Intersect.Framework.Core/Timing.cs
similarity index 98%
rename from Intersect (Core)/Utilities/Timing.cs
rename to Framework/Intersect.Framework.Core/Timing.cs
index 0d6efa6f78..0f747950b4 100644
--- a/Intersect (Core)/Utilities/Timing.cs
+++ b/Framework/Intersect.Framework.Core/Timing.cs
@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
-namespace Intersect.Utilities;
+namespace Intersect.Framework.Core;
///
/// Utility class for timing.
diff --git a/Framework/Intersect.Framework/Collections/ConcurrentConditionalDequeue.cs b/Framework/Intersect.Framework/Collections/ConcurrentConditionalDequeue.cs
new file mode 100644
index 0000000000..6e977524f2
--- /dev/null
+++ b/Framework/Intersect.Framework/Collections/ConcurrentConditionalDequeue.cs
@@ -0,0 +1,127 @@
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Intersect.Framework.Collections;
+
+public class ConcurrentConditionalDequeue : IReadOnlyCollection, IProducerConsumerCollection
+{
+ private static readonly Func IsEqual =
+ typeof(TValue).IsValueType ? StructEquals : (a, b) => ReferenceEquals(a, b);
+
+ private readonly object _dequeueLock = new();
+ private readonly ConcurrentQueue _queue = [];
+
+ private ICollection QueueAsGenericCollection => _queue;
+
+ private IProducerConsumerCollection QueueAsProducerConsumerCollection => _queue;
+
+ public bool IsReadOnly => false;
+
+ public void CopyTo(TValue[] array, int arrayIndex)
+ {
+ _queue.CopyTo(array, arrayIndex);
+ }
+
+ public TValue[] ToArray()
+ {
+ return _queue.ToArray();
+ }
+
+ bool IProducerConsumerCollection.TryAdd(TValue item)
+ {
+ return QueueAsProducerConsumerCollection.TryAdd(item);
+ }
+
+ bool IProducerConsumerCollection.TryTake([MaybeNullWhen(false)] out TValue item)
+ {
+ lock (_dequeueLock)
+ {
+ return QueueAsProducerConsumerCollection.TryTake(out item);
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ QueueAsGenericCollection.CopyTo(array, index);
+ }
+
+ bool ICollection.IsSynchronized => QueueAsProducerConsumerCollection.IsSynchronized;
+
+ object ICollection.SyncRoot => QueueAsProducerConsumerCollection.SyncRoot;
+
+ public int Count => _queue.Count;
+
+ public IEnumerator GetEnumerator()
+ {
+ return _queue.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void Clear()
+ {
+ lock (_dequeueLock)
+ {
+ _queue.Clear();
+ }
+ }
+
+ public void Enqueue(TValue item)
+ {
+ _queue.Enqueue(item);
+ }
+
+ public bool TryDequeueIf(Func consumer)
+ {
+ return TryDequeueIf(consumer, out _);
+ }
+
+ public bool TryDequeueIf(Func consumer, [NotNullWhen(true)] out TValue? value)
+ {
+ ArgumentNullException.ThrowIfNull(consumer);
+
+ lock (_dequeueLock)
+ {
+ if (!_queue.TryPeek(out value))
+ {
+ return false;
+ }
+
+ if (value == null)
+ {
+ return false;
+ }
+
+ if (!consumer(value))
+ {
+ return false;
+ }
+
+ if (!_queue.TryDequeue(out var dequeuedValue))
+ {
+ throw new InvalidOperationException("Dequeue failed, collection may have been removed from or cleared without being locked.");
+ }
+
+ if (!IsEqual(value, dequeuedValue))
+ {
+ throw new InvalidOperationException("Unexpected dequeued value, collection may have been removed from or cleared without being locked.");
+ }
+
+ return true;
+ }
+ }
+
+ public bool TryPeek([NotNullWhen(true)] out TValue? value)
+ {
+ lock (_dequeueLock)
+ {
+ return _queue.TryPeek(out value);
+ }
+ }
+
+ private static bool StructEquals(TValue a, TValue b) => a!.Equals(b);
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Collections/ListExtensions.cs b/Framework/Intersect.Framework/Collections/ListExtensions.cs
new file mode 100644
index 0000000000..305e45652c
--- /dev/null
+++ b/Framework/Intersect.Framework/Collections/ListExtensions.cs
@@ -0,0 +1,109 @@
+namespace Intersect.Framework.Collections;
+
+public static class ListExtensions
+{
+ public static void AddSorted(this List @this, T item) where T : IComparable
+ {
+ if (@this.Count == 0)
+ {
+ @this.Add(item);
+ return;
+ }
+
+ if (@this[^1].CompareTo(item) <= 0)
+ {
+ @this.Add(item);
+ return;
+ }
+
+ if (@this[0].CompareTo(item) >= 0)
+ {
+ @this.Insert(0, item);
+ return;
+ }
+
+ int index = @this.BinarySearch(item);
+ if (index < 0)
+ {
+ index = ~index;
+ }
+ @this.Insert(index, item);
+ }
+
+ public static void Resort(this List @this, T item) where T : IComparable
+ {
+ if (@this.Count < 1)
+ {
+ @this.AddSorted(item);
+ return;
+ }
+
+ @this.Remove(item);
+ @this.AddSorted(item);
+ }
+
+ private sealed class KeyComparer(Func keySelector)
+ : IComparer where TKey : IComparable
+ {
+ public int Compare(TItem? x, TItem? y)
+ {
+ var keyX = keySelector(x);
+ var keyY = keySelector(y);
+
+ if (keyX is not null)
+ {
+ return keyX.CompareTo(keyY);
+ }
+
+ if (keyY is null)
+ {
+ return 0;
+ }
+
+ return -keyY.CompareTo(keyX);
+ }
+ }
+
+ public static void AddSorted(this List @this, TItem item, Func keySelector) where TKey : IComparable
+ {
+ if (@this.Count == 0)
+ {
+ @this.Add(item);
+ return;
+ }
+
+ KeyComparer comparer = new(keySelector);
+
+ if (comparer.Compare(@this[^1], item) <= 0)
+ {
+ @this.Add(item);
+ return;
+ }
+
+ if (comparer.Compare(@this[0], item) >= 0)
+ {
+ @this.Insert(0, item);
+ return;
+ }
+
+ int index = @this.BinarySearch(item, comparer);
+ if (index < 0)
+ {
+ index = ~index;
+ }
+ @this.Insert(index, item);
+ }
+
+ public static void Resort(this List @this, TItem item, Func keySelector)
+ where TKey : IComparable
+ {
+ if (@this.Count < 1)
+ {
+ @this.AddSorted(item, keySelector);
+ return;
+ }
+
+ @this.Remove(item);
+ @this.AddSorted(item, keySelector);
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Eventing/EventHandler`2.cs b/Framework/Intersect.Framework/Eventing/EventHandler`2.cs
new file mode 100644
index 0000000000..24c6fd9722
--- /dev/null
+++ b/Framework/Intersect.Framework/Eventing/EventHandler`2.cs
@@ -0,0 +1,3 @@
+namespace Intersect.Framework.Eventing;
+
+public delegate void EventHandler(TSender sender, TArgs args) where TArgs : EventArgs;
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Intersect.Framework.csproj b/Framework/Intersect.Framework/Intersect.Framework.csproj
index 74a0400d74..d34c4bcc40 100644
--- a/Framework/Intersect.Framework/Intersect.Framework.csproj
+++ b/Framework/Intersect.Framework/Intersect.Framework.csproj
@@ -1,6 +1,7 @@
+
@@ -32,4 +33,8 @@
+
+
+
+
diff --git a/Framework/Intersect.Framework/Reflection/ReflectionStrings.Designer.cs b/Framework/Intersect.Framework/Reflection/ReflectionStrings.Designer.cs
index 616e26defa..9c78d48b75 100755
--- a/Framework/Intersect.Framework/Reflection/ReflectionStrings.Designer.cs
+++ b/Framework/Intersect.Framework/Reflection/ReflectionStrings.Designer.cs
@@ -8,9 +8,6 @@
//------------------------------------------------------------------------------
namespace Intersect.Framework.Reflection {
- using System;
-
-
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
diff --git a/Framework/Intersect.Framework/SystemInformation/IGPUStatisticsProvider.cs b/Framework/Intersect.Framework/SystemInformation/IGPUStatisticsProvider.cs
new file mode 100644
index 0000000000..ac74fbd920
--- /dev/null
+++ b/Framework/Intersect.Framework/SystemInformation/IGPUStatisticsProvider.cs
@@ -0,0 +1,8 @@
+namespace Intersect.Framework.SystemInformation;
+
+public interface IGPUStatisticsProvider
+{
+ long? AvailableMemory { get; }
+
+ long? TotalMemory { get; }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/SystemInformation/PlatformStatistics.cs b/Framework/Intersect.Framework/SystemInformation/PlatformStatistics.cs
new file mode 100644
index 0000000000..42830b434a
--- /dev/null
+++ b/Framework/Intersect.Framework/SystemInformation/PlatformStatistics.cs
@@ -0,0 +1,44 @@
+using Hardware.Info;
+using Microsoft.Extensions.Logging;
+
+namespace Intersect.Framework.SystemInformation;
+
+public class PlatformStatistics
+{
+ private static readonly HardwareInfo HardwareInfo;
+
+ public static IGPUStatisticsProvider? GPUStatisticsProvider { get; set; }
+
+ public static ILogger? Logger { get; set; }
+
+ public static long AvailablePhysicalMemory => (long)HardwareInfo.MemoryStatus.AvailablePhysical;
+
+ public static long TotalPhysicalMemory => (long)HardwareInfo.MemoryStatus.TotalPhysical;
+
+ public static long AvailableGPUMemory => GPUStatisticsProvider?.AvailableMemory ?? AvailableSystemMemory;
+
+ public static long TotalGPUMemory => GPUStatisticsProvider?.TotalMemory ?? TotalSystemMemory;
+
+ public static long AvailableSystemMemory => (long)HardwareInfo.MemoryStatus.AvailableVirtual;
+
+ public static long TotalSystemMemory => (long)HardwareInfo.MemoryStatus.TotalVirtual;
+
+ public static void Refresh()
+ {
+ try
+ {
+ HardwareInfo.RefreshMemoryStatus();
+ }
+ catch
+ {
+ // Do nothing
+ }
+ }
+
+ static PlatformStatistics()
+ {
+ HardwareInfo = new HardwareInfo();
+
+ Refresh();
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.Return`1.cs b/Framework/Intersect.Framework/Threading/ActionQueue.Return`1.cs
new file mode 100644
index 0000000000..4bf5d38346
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.Return`1.cs
@@ -0,0 +1,40 @@
+namespace Intersect.Framework.Threading;
+
+partial class ActionQueue
+{
+ public TReturn EnqueueReturn(Func func, TState state)
+ {
+ if (IsActive)
+ {
+ return func(state);
+ }
+
+ Return @return = new(func);
+ Enqueue(
+ Return.Wrapper,
+ new State>(
+ Return.FuncWrapper,
+ state,
+ @return
+ )
+ );
+ return @return.Value;
+ }
+
+ private sealed record Return(Func Func)
+ {
+ public static readonly Action>> Wrapper = state =>
+ state.Action(
+ state.State0,
+ state.State1
+ );
+
+ public static readonly Action> FuncWrapper =
+ (state, returnValue) =>
+ {
+ returnValue.Value = returnValue.Func(state);
+ };
+
+ public TReturn Value { get; private set; } = default!;
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.Return`2.cs b/Framework/Intersect.Framework/Threading/ActionQueue.Return`2.cs
new file mode 100644
index 0000000000..7d8b73d0e3
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.Return`2.cs
@@ -0,0 +1,43 @@
+namespace Intersect.Framework.Threading;
+
+partial class ActionQueue
+{
+ public TReturn EnqueueReturn(
+ Func func,
+ TState0 state0,
+ TState1 state1
+ )
+ {
+ if (IsActive)
+ {
+ return func(state0, state1);
+ }
+
+ Return @return = new(func);
+ Enqueue(
+ Return.Wrapper,
+ new State>(
+ Return.FuncWrapper,
+ state0,
+ state1,
+ @return
+ )
+ );
+ return @return.Value;
+ }
+
+ private sealed record Return(Func Func)
+ {
+ public static readonly Action>>
+ Wrapper =
+ state => state.Action(state.State0, state.State1, state.State2);
+
+ public static readonly Action> FuncWrapper =
+ (state0, state1, returnValue) =>
+ {
+ returnValue.Value = returnValue.Func(state0, state1);
+ };
+
+ public TReturn Value { get; private set; } = default!;
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.State`1.cs b/Framework/Intersect.Framework/Threading/ActionQueue.State`1.cs
new file mode 100644
index 0000000000..ca36c30490
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.State`1.cs
@@ -0,0 +1,30 @@
+namespace Intersect.Framework.Threading;
+
+public abstract partial class ActionQueue
+{
+ private record struct State
+ {
+ public static readonly Queue ActionQueue = [];
+
+ // ReSharper disable once StaticMemberInGenericType
+ public static readonly Action Next = InternalNext;
+
+ private static void InternalNext()
+ {
+ if (!ActionQueue.TryDequeue(out var deferredAction))
+ {
+ throw new InvalidOperationException("Action queue is not being properly synchronized");
+ }
+
+ deferredAction.Action(deferredAction.ActionState);
+ deferredAction.PostInvocationAction(deferredAction.EnqueueState);
+ }
+
+ public record struct DeferredAction(
+ Action Action,
+ TState ActionState,
+ Action PostInvocationAction,
+ TEnqueueState EnqueueState
+ );
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.State`2.cs b/Framework/Intersect.Framework/Threading/ActionQueue.State`2.cs
new file mode 100644
index 0000000000..4a7994b0f1
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.State`2.cs
@@ -0,0 +1,26 @@
+namespace Intersect.Framework.Threading;
+
+partial class ActionQueue
+{
+ public void Enqueue(Action action, TState0 state0, TState1 state1)
+ {
+ if (IsActive)
+ {
+ action(state0, state1);
+ return;
+ }
+
+ Enqueue(
+ State.Wrapper,
+ new State(action, state0, state1)
+ );
+ }
+
+ private record struct State(Action Action, TState0 State0, TState1 State1)
+ {
+ public static readonly Action> Wrapper = state => state.Action(
+ state.State0,
+ state.State1
+ );
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.State`3.cs b/Framework/Intersect.Framework/Threading/ActionQueue.State`3.cs
new file mode 100644
index 0000000000..39a098b7b3
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.State`3.cs
@@ -0,0 +1,37 @@
+namespace Intersect.Framework.Threading;
+
+partial class ActionQueue
+{
+ public void Enqueue(
+ Action action,
+ TState0 state0,
+ TState1 state1,
+ TState2 state2
+ )
+ {
+ if (IsActive)
+ {
+ action(state0, state1, state2);
+ return;
+ }
+
+ Enqueue(
+ State.Wrapper,
+ new State(action, state0, state1, state2)
+ );
+ }
+
+ private record struct State(
+ Action Action,
+ TState0 State0,
+ TState1 State1,
+ TState2 State2
+ )
+ {
+ public static readonly Action> Wrapper = state => state.Action(
+ state.State0,
+ state.State1,
+ state.State2
+ );
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ActionQueue.cs b/Framework/Intersect.Framework/Threading/ActionQueue.cs
new file mode 100644
index 0000000000..7228df8a17
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ActionQueue.cs
@@ -0,0 +1,116 @@
+namespace Intersect.Framework.Threading;
+
+public abstract partial class ActionQueue where TActionQueue : ActionQueue
+{
+ private readonly TActionQueue @this;
+ private readonly Queue _actionQueue = [];
+ private readonly Action _statelessAction = action => action();
+ private readonly Action? _beginInvokePending;
+ private readonly Action? _endInvokePending;
+
+ private bool _empty;
+
+ protected ActionQueue(Action? beginInvokePending, Action? endInvokePending)
+ {
+ @this = this as TActionQueue ?? throw new InvalidCastException();
+ _beginInvokePending = beginInvokePending;
+ _endInvokePending = endInvokePending;
+ }
+
+ public event Action? QueueNotEmpty;
+
+ protected abstract bool IsActive { get; }
+
+ protected abstract Action PostInvocationAction { get; }
+
+ ///
+ ///
+ ///
+ /// Returns true if the queue was non-empty and is now empty.
+ public bool InvokePending()
+ {
+ if (_empty)
+ {
+ return false;
+ }
+
+ _beginInvokePending?.Invoke(@this);
+
+ _empty = true;
+
+ lock (_actionQueue)
+ {
+ while (_actionQueue.TryDequeue(out var deferredAction))
+ {
+ deferredAction();
+ }
+ }
+
+ _endInvokePending?.Invoke(@this);
+ return _actionQueue.Count < 1;
+ }
+
+ public void Enqueue(Action action)
+ {
+ if (IsActive)
+ {
+ action();
+ return;
+ }
+
+ Enqueue(_statelessAction, action);
+ }
+
+ public void Enqueue(Action action, TState state)
+ {
+ ArgumentNullException.ThrowIfNull(action, nameof(action));
+
+ if (IsActive)
+ {
+ action(state);
+ return;
+ }
+
+ var enqueueState = EnqueueCreateState();
+
+ try
+ {
+ State.DeferredAction deferredAction = new(
+ action,
+ state,
+ PostInvocationAction,
+ enqueueState
+ );
+
+ lock (_actionQueue)
+ {
+ State.ActionQueue.Enqueue(deferredAction);
+ _actionQueue.Enqueue(State.Next);
+ try
+ {
+ if (_empty)
+ {
+ QueueNotEmpty?.Invoke();
+ }
+ }
+ catch
+ {
+ // Ignore, application context not available here
+ }
+ _empty = false;
+ }
+
+ EnqueueSuccessful(enqueueState);
+ }
+ finally
+ {
+ EnqueueFinally(enqueueState);
+ }
+ }
+
+ protected abstract TEnqueueState EnqueueCreateState();
+
+ protected abstract void EnqueueSuccessful(TEnqueueState enqueueState);
+
+ protected abstract void EnqueueFinally(TEnqueueState enqueueState);
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ManualActionQueue.cs b/Framework/Intersect.Framework/Threading/ManualActionQueue.cs
new file mode 100644
index 0000000000..e24cd3ac88
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ManualActionQueue.cs
@@ -0,0 +1,69 @@
+namespace Intersect.Framework.Threading;
+
+public interface IManualActionQueueParent
+{
+ bool IsExecuting { get; }
+}
+
+public sealed class ManualActionQueueParent : IManualActionQueueParent
+{
+ public bool IsExecuting { get; set; }
+}
+
+public sealed class ManualActionQueue : ActionQueue
+{
+ private readonly object _lock = new();
+
+ private bool _active;
+ private readonly IManualActionQueueParent? _parent;
+
+ public ManualActionQueue(IManualActionQueueParent? parent = null) : base(
+ beginInvokePending: BeginInvokePending,
+ endInvokePending: EndInvokePending
+ )
+ {
+ _parent = parent;
+ }
+
+ // Can't actually convert it because we can't add a private setter here
+ // ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
+ protected override bool IsActive => _active;
+
+ protected override Action PostInvocationAction => static _ => { };
+
+ private static void BeginInvokePending(ManualActionQueue @this)
+ {
+ if (@this._parent is { IsExecuting: false } || !Monitor.TryEnter(@this._lock))
+ {
+ throw new InvalidOperationException("Tried to invoke pending actions from an invalid source");
+ }
+
+ @this._active = true;
+ }
+
+ private static void EndInvokePending(ManualActionQueue @this)
+ {
+ @this._active = false;
+
+ Monitor.Exit(@this._lock);
+ }
+
+ protected override bool EnqueueCreateState()
+ {
+ var lockTaken = false;
+ Monitor.Enter(_lock, ref lockTaken);
+ return lockTaken;
+ }
+
+ protected override void EnqueueSuccessful(bool lockTaken)
+ {
+ }
+
+ protected override void EnqueueFinally(bool lockTaken)
+ {
+ if (lockTaken)
+ {
+ Monitor.Exit(_lock);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Framework/Intersect.Framework/Threading/ThreadQueue.cs b/Framework/Intersect.Framework/Threading/ThreadQueue.cs
new file mode 100644
index 0000000000..6daf146119
--- /dev/null
+++ b/Framework/Intersect.Framework/Threading/ThreadQueue.cs
@@ -0,0 +1,123 @@
+using System.Runtime.CompilerServices;
+
+namespace Intersect.Framework.Threading;
+
+public sealed partial class ThreadQueue : ActionQueue
+{
+ public static readonly ThreadQueue Default = new();
+
+ private readonly object _lock = new();
+ private readonly Stack _resetEventPool = [];
+ private readonly int? _spinCount;
+
+ private int _mainThreadId;
+
+ public ThreadQueue(int? spinCount = null) : base(beginInvokePending: BeginInvokePending, endInvokePending: null)
+ {
+ _spinCount = spinCount;
+
+ SetMainThreadId();
+ }
+
+ public ThreadQueue(ThreadQueue parent) : base(beginInvokePending: BeginInvokePending, endInvokePending: null)
+ {
+ _spinCount = parent._spinCount;
+ _mainThreadId = parent._mainThreadId;
+ }
+
+ protected override bool IsActive => IsOnMainThread;
+
+ public bool IsOnMainThread
+ {
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _mainThreadId == Environment.CurrentManagedThreadId;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BeginInvokePending(ThreadQueue @this) => @this.ThrowIfNotOnMainThread();
+
+ protected override ManualResetEventSlim EnqueueCreateState() => ResetEventPoolPop();
+
+ protected override void EnqueueSuccessful(ManualResetEventSlim resetEvent) => resetEvent.Wait();
+
+ protected override void EnqueueFinally(ManualResetEventSlim resetEvent) => ResetEventPoolPush(resetEvent);
+
+ protected override Action PostInvocationAction { get; } = static resetEvent => resetEvent.Set();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RunOnMainThread(Action action) => Enqueue(action);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RunOnMainThread(Action action, TState state) => Enqueue(action, state);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TReturn RunOnMainThread(Func func, TState state) => EnqueueReturn(func, state);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RunOnMainThread(Action action, TState0 state0, TState1 state1) =>
+ Enqueue(action, state0, state1);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TReturn RunOnMainThread(Func func, TState0 state0, TState1 state1) =>
+ EnqueueReturn(func, state0, state1);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RunOnMainThread(
+ Action action,
+ TState0 state0,
+ TState1 state1,
+ TState2 state2
+ ) => Enqueue(action, state0, state1, state2);
+
+ private ManualResetEventSlim ResetEventPoolPop()
+ {
+ lock (_resetEventPool)
+ {
+ if (_resetEventPool.TryPop(out var resetEvent))
+ {
+ return resetEvent;
+ }
+ }
+
+ return _spinCount.HasValue
+ ? new ManualResetEventSlim(false, _spinCount.Value)
+ : new ManualResetEventSlim();
+ }
+
+ private void ResetEventPoolPush(ManualResetEventSlim resetEvent)
+ {
+ resetEvent.Reset();
+ lock (_resetEventPool)
+ {
+ _resetEventPool.Push(resetEvent);
+ }
+ }
+
+ public void SetMainThreadId(int? mainThreadId = null)
+ {
+ lock (_lock)
+ {
+ _mainThreadId = mainThreadId ?? Environment.CurrentManagedThreadId;
+ }
+ }
+
+ public void SetMainThreadId(ThreadQueue other)
+ {
+ lock (_lock)
+ {
+ _mainThreadId = other._mainThreadId;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ThrowIfNotOnMainThread()
+ {
+ if (IsOnMainThread)
+ {
+ return;
+ }
+
+ throw new InvalidOperationException("Operation was not called on the main thread.");
+ }
+}
\ No newline at end of file
diff --git a/Intersect (Core)/Core/ApplicationContext`2.cs b/Intersect (Core)/Core/ApplicationContext`2.cs
index 6ed277bd34..6e4fe792dc 100644
--- a/Intersect (Core)/Core/ApplicationContext`2.cs
+++ b/Intersect (Core)/Core/ApplicationContext`2.cs
@@ -22,31 +22,36 @@ public abstract partial class ApplicationContext : IA
{
#region Lifecycle Fields
- private bool mIsRunning;
+ private bool _running;
- private bool mNeedsLockPulse;
+ private bool _needsLockPulse;
- private readonly object mDisposeLock;
+ private readonly object _disposeLock = new();
- private readonly object mShutdownLock;
+ private readonly object _shutdownLock = new();
#endregion Lifecycle Fields
///
/// Initializes general pieces of the .
///
+ ///
/// the the application was started with
/// the application-level
///
- protected ApplicationContext(TStartupOptions startupOptions, ILogger logger, IPacketHelper packetHelper)
+ ///
+ protected ApplicationContext(
+ Assembly entryAssembly,
+ string fallbackName,
+ TStartupOptions startupOptions,
+ ILogger logger,
+ IPacketHelper packetHelper
+ )
{
- mDisposeLock = new object();
- mShutdownLock = new object();
+ Name = entryAssembly.GetName().Name ?? fallbackName;
ApplicationContext.Context.Value = this;
- mServices = new ConcurrentDictionary();
-
StartupOptions = startupOptions;
Logger = logger;
PacketHelper = packetHelper;
@@ -54,6 +59,8 @@ protected ApplicationContext(TStartupOptions startupOptions, ILogger logger, IPa
ConcurrentInstance.Set(This);
}
+ public string Name { get; }
+
ICommandLineOptions IApplicationContext.StartupOptions => StartupOptions;
///
@@ -91,15 +98,15 @@ protected ApplicationContext(TStartupOptions startupOptions, ILogger logger, IPa
///
public bool IsRunning
{
- get => mIsRunning && !IsShutdownRequested;
- private set => mIsRunning = value;
+ get => _running && !IsShutdownRequested;
+ private set => _running = value;
}
#endregion Lifecycle Properties
#region Services
- private readonly IDictionary mServices;
+ private readonly IDictionary mServices = new ConcurrentDictionary();
///
public List Services => mServices.Values.ToList();
@@ -257,16 +264,16 @@ public void Start(bool lockUntilShutdown = true)
#region Wait for application thread
- mNeedsLockPulse = lockUntilShutdown;
+ _needsLockPulse = lockUntilShutdown;
- if (!mNeedsLockPulse)
+ if (!_needsLockPulse)
{
return;
}
- lock (mShutdownLock)
+ lock (_shutdownLock)
{
- Monitor.Wait(mShutdownLock);
+ Monitor.Wait(_shutdownLock);
ApplicationContext.Context.Value?.Logger.LogTrace(DeveloperStrings.ApplicationContextExited);
}
@@ -284,9 +291,9 @@ public ILockingActionQueue StartWithActionQueue()
{
Start(false);
- mNeedsLockPulse = true;
+ _needsLockPulse = true;
- return new LockingActionQueue(mShutdownLock);
+ return new LockingActionQueue(_shutdownLock);
}
///
@@ -302,7 +309,7 @@ public void RequestShutdown(bool join = false)
{
Task disposeTask;
- lock (mDisposeLock)
+ lock (_disposeLock)
{
if (IsDisposed || IsDisposing || IsShutdownRequested)
{
@@ -315,9 +322,9 @@ public void RequestShutdown(bool join = false)
{
Dispose();
- lock (mShutdownLock)
+ lock (_shutdownLock)
{
- Monitor.PulseAll(mShutdownLock);
+ Monitor.PulseAll(_shutdownLock);
}
}
);
@@ -513,7 +520,7 @@ public void Dispose()
throw new ObjectDisposedException(typeof(TContext).Name);
}
- lock (mDisposeLock)
+ lock (_disposeLock)
{
if (IsDisposing)
{
diff --git a/Intersect (Core)/Intersect.Core.csproj b/Intersect (Core)/Intersect.Core.csproj
index 011e14d326..16fe42810d 100644
--- a/Intersect (Core)/Intersect.Core.csproj
+++ b/Intersect (Core)/Intersect.Core.csproj
@@ -68,7 +68,6 @@
-
diff --git a/Intersect (Core)/Network/AbstractNetwork.cs b/Intersect (Core)/Network/AbstractNetwork.cs
index fd24f79cf6..d25a5a1626 100644
--- a/Intersect (Core)/Network/AbstractNetwork.cs
+++ b/Intersect (Core)/Network/AbstractNetwork.cs
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Net;
using Intersect.Core;
+using Intersect.Framework.Core;
using Intersect.Framework.Reflection;
using Intersect.Memory;
using Intersect.Plugins.Interfaces;
diff --git a/Intersect (Core)/Network/ConnectionPacket.cs b/Intersect (Core)/Network/ConnectionPacket.cs
index 2b9e074a87..d988c87c0e 100644
--- a/Intersect (Core)/Network/ConnectionPacket.cs
+++ b/Intersect (Core)/Network/ConnectionPacket.cs
@@ -1,4 +1,5 @@
using System.Security.Cryptography;
+using Intersect.Framework.Core;
using Intersect.Network.Packets;
using Intersect.Utilities;
using MessagePack;
diff --git a/Intersect (Core)/Network/Packets/AbstractTimedPacket.cs b/Intersect (Core)/Network/Packets/AbstractTimedPacket.cs
index 146fa6ccb6..4172977068 100644
--- a/Intersect (Core)/Network/Packets/AbstractTimedPacket.cs
+++ b/Intersect (Core)/Network/Packets/AbstractTimedPacket.cs
@@ -1,4 +1,5 @@
-using Intersect.Network.Packets.Client;
+using Intersect.Framework.Core;
+using Intersect.Network.Packets.Client;
using Intersect.Network.Packets.Server;
using Intersect.Utilities;
using MessagePack;
diff --git a/Intersect.Client.Core/Core/Audio.cs b/Intersect.Client.Core/Core/Audio.cs
index 9b9ca0720a..345c0b2107 100644
--- a/Intersect.Client.Core/Core/Audio.cs
+++ b/Intersect.Client.Core/Core/Audio.cs
@@ -5,7 +5,7 @@
using Intersect.Client.Framework.File_Management;
using Intersect.Client.General;
using Intersect.Core;
-using Intersect.Utilities;
+using Intersect.Framework.Core;
using Microsoft.Extensions.Logging;
namespace Intersect.Client.Core;
diff --git a/Intersect.Client.Core/Core/Bootstrapper.cs b/Intersect.Client.Core/Core/Bootstrapper.cs
index 1031d9a7d0..734a500fc7 100644
--- a/Intersect.Client.Core/Core/Bootstrapper.cs
+++ b/Intersect.Client.Core/Core/Bootstrapper.cs
@@ -5,6 +5,7 @@
using Intersect.Core;
using Intersect.Factories;
using Intersect.Framework.Logging;
+using Intersect.Framework.SystemInformation;
using Intersect.Network;
using Intersect.Plugins;
using Intersect.Plugins.Contexts;
@@ -19,7 +20,7 @@ namespace Intersect.Client.Core;
internal static partial class Bootstrapper
{
- public static void Start(params string[] args)
+ public static void Start(Assembly entryAssembly, params string[] args)
{
var parser = new Parser(
parserSettings =>
@@ -43,13 +44,14 @@ public static void Start(params string[] args)
LoggingLevelSwitch loggingLevelSwitch =
new(Debugger.IsAttached ? LogEventLevel.Debug : LogEventLevel.Information);
- var executingAssembly = Assembly.GetExecutingAssembly();
- var (_, logger) = new LoggerConfiguration().CreateLoggerForIntersect(
- executingAssembly,
+ var (loggerFactory, logger) = new LoggerConfiguration().CreateLoggerForIntersect(
+ entryAssembly,
"Client",
loggingLevelSwitch
);
+ PlatformStatistics.Logger = loggerFactory.CreateLogger();
+
var packetTypeRegistry = new PacketTypeRegistry(logger, typeof(SharedConstants).Assembly);
if (!packetTypeRegistry.TryRegisterBuiltIn())
{
@@ -71,7 +73,11 @@ public static void Start(params string[] args)
}
else
{
- ApplicationContext.Context.Value?.Logger.LogWarning($"Failed to set working directory to '{workingDirectory}', path does not exist: {resolvedWorkingDirectory}");
+ ApplicationContext.Context.Value?.Logger.LogWarning(
+ "Failed to set working directory to '{Path}', path does not exist: {ResolvedPath}",
+ workingDirectory,
+ resolvedWorkingDirectory
+ );
}
}
@@ -99,7 +105,7 @@ public static void Start(params string[] args)
Server = $"{clientConfiguration.Host}:{clientConfiguration.Port}",
};
- ClientContext context = new(commandLineOptions, clientConfiguration, logger, packetHelper);
+ ClientContext context = new(entryAssembly, commandLineOptions, clientConfiguration, logger, packetHelper);
context.Start();
}
@@ -108,9 +114,9 @@ private static ClientCommandLineOptions HandleParsedArguments(ClientCommandLineO
private static ClientCommandLineOptions HandleParserErrors(IEnumerable errors)
{
- var errorsAsList = errors?.ToList();
- var fatalParsingError = errorsAsList?.Any(error => error?.StopsProcessing ?? false) ?? false;
- var errorString = string.Join(", ", errorsAsList?.ToList().Select(error => error?.ToString()) ?? []);
+ var errorsAsList = errors.ToList();
+ var fatalParsingError = errorsAsList.Any(error => error.StopsProcessing);
+ var errorString = string.Join(", ", errorsAsList.ToList().Select(error => error.ToString()));
var exception = new ArgumentException(
$@"Error parsing command line arguments, received the following errors: {errorString}"
diff --git a/Intersect.Client.Core/Core/ClientContext.cs b/Intersect.Client.Core/Core/ClientContext.cs
index 2b783c9a61..fc55c23599 100644
--- a/Intersect.Client.Core/Core/ClientContext.cs
+++ b/Intersect.Client.Core/Core/ClientContext.cs
@@ -1,5 +1,6 @@
using System.Net;
using System.Net.Sockets;
+using System.Reflection;
using Intersect.Client.Networking;
using Intersect.Client.Plugins.Contexts;
using Intersect.Configuration;
@@ -23,9 +24,13 @@ internal sealed partial class ClientContext : ApplicationContext[,]? RenderingEntities;
private static GameContentManager sContentManager = null!;
- private static GameRenderTexture? sDarknessTexture;
+ private static IGameRenderTexture? sDarknessTexture;
private static readonly List sLightQueue = [];
@@ -531,36 +532,38 @@ public static void DrawInGame(TimeSpan deltaTime)
//Game Rendering
public static void Render(TimeSpan deltaTime, TimeSpan totalTime)
{
- var takingScreenshot = false;
- if (Renderer?.ScreenshotRequests.Count > 0)
+ if (Renderer is not { } renderer)
{
- takingScreenshot = Renderer.BeginScreenshot();
+ return;
}
- if (Renderer == default)
+ var takingScreenshot = false;
+ if (renderer is { HasScreenshotRequests: true })
{
- return;
+ takingScreenshot = renderer.BeginScreenshot();
}
- Renderer.Scale = Globals.GameState == GameStates.InGame ? Globals.Database.WorldZoom : 1.0f;
+ var gameState = Globals.GameState;
- if (!Renderer.Begin())
+ renderer.Scale = gameState == GameStates.InGame ? Globals.Database.WorldZoom : 1.0f;
+
+ if (!renderer.Begin())
{
return;
}
- if (Renderer.GetScreenWidth() != sOldWidth ||
- Renderer.GetScreenHeight() != sOldHeight ||
- Renderer.DisplayModeChanged())
+ if (renderer.ScreenWidth != sOldWidth ||
+ renderer.ScreenHeight != sOldHeight ||
+ renderer.DisplayModeChanged())
{
sDarknessTexture = null;
Interface.Interface.DestroyGwen();
Interface.Interface.InitGwen();
- sOldWidth = Renderer.GetScreenWidth();
- sOldHeight = Renderer.GetScreenHeight();
+ sOldWidth = renderer.ScreenWidth;
+ sOldHeight = renderer.ScreenHeight;
}
- Renderer.Clear(Color.Black);
+ renderer.Clear(Color.Black);
DrawCalls = 0;
MapsDrawn = 0;
EntitiesDrawn = 0;
@@ -568,51 +571,67 @@ public static void Render(TimeSpan deltaTime, TimeSpan totalTime)
UpdateView();
- switch (Globals.GameState)
+ switch (gameState)
{
case GameStates.Intro:
DrawIntro();
-
break;
+
case GameStates.Menu:
DrawMenu();
-
break;
+
case GameStates.Loading:
break;
+
case GameStates.InGame:
DrawInGame(deltaTime);
-
break;
+
case GameStates.Error:
break;
+
default:
- throw new ArgumentOutOfRangeException();
+ throw Exceptions.UnreachableInvalidEnum(gameState);
}
- Renderer.Scale = Globals.Database.UIScale;
+ renderer.Scale = Globals.Database.UIScale;
Interface.Interface.DrawGui(deltaTime, totalTime);
DrawGameTexture(
- Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1), CurrentView,
- new Color((int)Fade.Alpha, 0, 0, 0), null, GameBlendModes.None
+ tex: renderer.WhitePixel,
+ srcRectangle: new FloatRect(0, 0, 1, 1),
+ targetRect: CurrentView,
+ renderColor: new Color((int)Fade.Alpha, 0, 0, 0),
+ renderTarget: null,
+ blendMode: GameBlendModes.None
);
// Draw our mousecursor at the very end, but not when taking screenshots.
if (!takingScreenshot && !string.IsNullOrWhiteSpace(ClientConfiguration.Instance.MouseCursor))
{
- var renderLoc = ConvertToWorldPointNoZoom(Globals.InputManager.GetMousePosition());
- DrawGameTexture(
- Globals.ContentManager.GetTexture(Framework.Content.TextureType.Misc, ClientConfiguration.Instance.MouseCursor), renderLoc.X, renderLoc.Y
- );
+ var cursorTexture = Globals.ContentManager.GetTexture(
+ TextureType.Misc,
+ ClientConfiguration.Instance.MouseCursor
+ );
+
+ if (cursorTexture is not null)
+ {
+ var cursorPosition = ConvertToWorldPointNoZoom(Globals.InputManager.GetMousePosition());
+ DrawGameTexture(
+ cursorTexture,
+ cursorPosition.X,
+ cursorPosition.Y
+ );
+ }
}
- Renderer.End();
+ renderer.End();
if (takingScreenshot)
{
- Renderer.EndScreenshot();
+ renderer.EndScreenshot();
}
}
@@ -774,40 +793,40 @@ public static void DrawOverlay()
}
}
- DrawGameTexture(Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1), CurrentView, OverlayColor, null);
+ DrawGameTexture(Renderer.WhitePixel, new FloatRect(0, 0, 1, 1), CurrentView, OverlayColor, null);
sOverlayUpdate = Timing.Global.MillisecondsUtc;
}
- public static FloatRect GetSourceRect(GameTexture gameTexture)
+ public static FloatRect GetSourceRect(IGameTexture gameTexture)
{
return gameTexture == null
? new FloatRect()
: new FloatRect(0, 0, gameTexture.Width, gameTexture.Height);
}
- public static void DrawFullScreenTexture(GameTexture tex, float alpha = 1f)
+ public static void DrawFullScreenTexture(IGameTexture tex, float alpha = 1f)
{
if (Renderer == default)
{
return;
}
- var bgx = Renderer.GetScreenWidth() / 2 - tex.Width / 2;
- var bgy = Renderer.GetScreenHeight() / 2 - tex.Height / 2;
+ var bgx = Renderer.ScreenWidth / 2 - tex.Width / 2;
+ var bgy = Renderer.ScreenHeight / 2 - tex.Height / 2;
var bgw = tex.Width;
var bgh = tex.Height;
int diff;
- if (bgw < Renderer.GetScreenWidth())
+ if (bgw < Renderer.ScreenWidth)
{
- diff = Renderer.GetScreenWidth() - bgw;
+ diff = Renderer.ScreenWidth - bgw;
bgx -= diff / 2;
bgw += diff;
}
- if (bgh < Renderer.GetScreenHeight())
+ if (bgh < Renderer.ScreenHeight)
{
- diff = Renderer.GetScreenHeight() - bgh;
+ diff = Renderer.ScreenHeight - bgh;
bgy -= diff / 2;
bgh += diff;
}
@@ -819,15 +838,15 @@ public static void DrawFullScreenTexture(GameTexture tex, float alpha = 1f)
);
}
- public static void DrawFullScreenTextureCentered(GameTexture tex, float alpha = 1f)
+ public static void DrawFullScreenTextureCentered(IGameTexture tex, float alpha = 1f)
{
if (Renderer == default)
{
return;
}
- var bgx = Renderer.GetScreenWidth() / 2 - tex.Width / 2;
- var bgy = Renderer.GetScreenHeight() / 2 - tex.Height / 2;
+ var bgx = Renderer.ScreenWidth / 2 - tex.Width / 2;
+ var bgy = Renderer.ScreenHeight / 2 - tex.Height / 2;
var bgw = tex.Width;
var bgh = tex.Height;
@@ -838,7 +857,7 @@ public static void DrawFullScreenTextureCentered(GameTexture tex, float alpha =
);
}
- public static void DrawFullScreenTextureStretched(GameTexture tex)
+ public static void DrawFullScreenTextureStretched(IGameTexture tex)
{
if (Renderer == default)
{
@@ -848,55 +867,55 @@ public static void DrawFullScreenTextureStretched(GameTexture tex)
DrawGameTexture(
tex, GetSourceRect(tex),
new FloatRect(
- Renderer.GetView().X, Renderer.GetView().Y, Renderer.GetScreenWidth(), Renderer.GetScreenHeight()
+ Renderer.GetView().X, Renderer.GetView().Y, Renderer.ScreenWidth, Renderer.ScreenHeight
), Color.White
);
}
- public static void DrawFullScreenTextureFitWidth(GameTexture tex)
+ public static void DrawFullScreenTextureFitWidth(IGameTexture tex)
{
if (Renderer == default)
{
return;
}
- var scale = Renderer.GetScreenWidth() / (float)tex.Width;
+ var scale = Renderer.ScreenWidth / (float)tex.Width;
var scaledHeight = tex.Height * scale;
- var offsetY = (Renderer.GetScreenHeight() - tex.Height) / 2f;
+ var offsetY = (Renderer.ScreenHeight - tex.Height) / 2f;
DrawGameTexture(
tex, GetSourceRect(tex),
new FloatRect(
- Renderer.GetView().X, Renderer.GetView().Y + offsetY, Renderer.GetScreenWidth(), scaledHeight
+ Renderer.GetView().X, Renderer.GetView().Y + offsetY, Renderer.ScreenWidth, scaledHeight
), Color.White
);
}
- public static void DrawFullScreenTextureFitHeight(GameTexture tex)
+ public static void DrawFullScreenTextureFitHeight(IGameTexture tex)
{
if (Renderer == default)
{
return;
}
- var scale = Renderer.GetScreenHeight() / (float)tex.Height;
+ var scale = Renderer.ScreenHeight / (float)tex.Height;
var scaledWidth = tex.Width * scale;
- var offsetX = (Renderer.GetScreenWidth() - scaledWidth) / 2f;
+ var offsetX = (Renderer.ScreenWidth - scaledWidth) / 2f;
DrawGameTexture(
tex, GetSourceRect(tex),
new FloatRect(
- Renderer.GetView().X + offsetX, Renderer.GetView().Y, scaledWidth, Renderer.GetScreenHeight()
+ Renderer.GetView().X + offsetX, Renderer.GetView().Y, scaledWidth, Renderer.ScreenHeight
), Color.White
);
}
- public static void DrawFullScreenTextureFitMinimum(GameTexture tex)
+ public static void DrawFullScreenTextureFitMinimum(IGameTexture tex)
{
if (Renderer == default)
{
return;
}
- if (Renderer.GetScreenWidth() > Renderer.GetScreenHeight())
+ if (Renderer.ScreenWidth > Renderer.ScreenHeight)
{
DrawFullScreenTextureFitHeight(tex);
}
@@ -906,14 +925,14 @@ public static void DrawFullScreenTextureFitMinimum(GameTexture tex)
}
}
- public static void DrawFullScreenTextureFitMaximum(GameTexture tex)
+ public static void DrawFullScreenTextureFitMaximum(IGameTexture tex)
{
if (Renderer == default)
{
return;
}
- if (Renderer.GetScreenWidth() < Renderer.GetScreenHeight())
+ if (Renderer.ScreenWidth < Renderer.ScreenHeight)
{
DrawFullScreenTextureFitHeight(tex);
}
@@ -934,8 +953,8 @@ private static void UpdateView()
if (Globals.GameState != GameStates.InGame || !MapInstance.TryGet(Globals.Me?.MapId ?? Guid.Empty, out var map))
{
- var sw = Renderer.GetScreenWidth();
- var sh = Renderer.GetScreenHeight();
+ var sw = Renderer.ScreenWidth;
+ var sh = Renderer.ScreenHeight;
var sx = 0;
var sy = 0;
CurrentView = new FloatRect(sx, sy, sw / scale, sh / scale);
@@ -1043,7 +1062,7 @@ private static void ClearDarknessTexture()
return;
}
- sDarknessTexture ??= Renderer.CreateRenderTexture(Renderer.GetScreenWidth(), Renderer.GetScreenHeight());
+ sDarknessTexture ??= Renderer.CreateRenderTexture(Renderer.ScreenWidth, Renderer.ScreenHeight);
sDarknessTexture.Clear(Color.Black);
}
@@ -1079,7 +1098,7 @@ private static void GenerateLightMap()
if (map.IsIndoors)
{
DrawGameTexture(
- Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
destRect,
new Color((byte)BrightnessLevel, 255, 255, 255), sDarknessTexture, GameBlendModes.Add
);
@@ -1087,13 +1106,13 @@ private static void GenerateLightMap()
else
{
DrawGameTexture(
- Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
destRect,
new Color(255, 255, 255, 255), sDarknessTexture, GameBlendModes.Add
);
DrawGameTexture(
- Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
destRect,
new Color(
(int)Time.GetTintColor().A, (int)Time.GetTintColor().R, (int)Time.GetTintColor().G,
@@ -1175,7 +1194,7 @@ private static void DrawLights()
radialShader.SetFloat("Expand", l.Expand / 100f);
DrawGameTexture(
- Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
new FloatRect(x, y, l.Size * 2, l.Size * 2), new Color(255, 255, 255, 255), sDarknessTexture, GameBlendModes.Add, radialShader, 0, false
);
@@ -1421,10 +1440,10 @@ public static Pointf ConvertToWorldPointNoZoom(Pointf windowPoint)
/// How much to rotate the texture in degrees
/// If true, the texture will be drawn immediately. If false, it will be queued for drawing.
public static void DrawGameTexture(
- GameTexture tex,
+ IGameTexture tex,
float x,
float y,
- GameRenderTexture? renderTarget = null,
+ IGameRenderTexture? renderTarget = null,
GameBlendModes blendMode = GameBlendModes.None,
GameShader? shader = null,
float rotationDegrees = 0.0f,
@@ -1454,11 +1473,11 @@ public static void DrawGameTexture(
/// How much to rotate the texture in degrees
/// If true, the texture will be drawn immediately. If false, it will be queued for drawing.
public static void DrawGameTexture(
- GameTexture tex,
+ IGameTexture tex,
float x,
float y,
Color renderColor,
- GameRenderTexture? renderTarget = null,
+ IGameRenderTexture? renderTarget = null,
GameBlendModes blendMode = GameBlendModes.None,
GameShader? shader = null,
float rotationDegrees = 0.0f,
@@ -1490,14 +1509,14 @@ public static void DrawGameTexture(
/// How much to rotate the texture in degrees
/// If true, the texture will be drawn immediately. If false, it will be queued for drawing.
public static void DrawGameTexture(
- GameTexture tex,
+ IGameTexture tex,
float dx,
float dy,
float sx,
float sy,
float w,
float h,
- GameRenderTexture? renderTarget = null,
+ IGameRenderTexture? renderTarget = null,
GameBlendModes blendMode = GameBlendModes.None,
GameShader? shader = null,
float rotationDegrees = 0.0f,
@@ -1516,11 +1535,11 @@ public static void DrawGameTexture(
}
public static void DrawGameTexture(
- GameTexture tex,
+ IGameTexture tex,
FloatRect srcRectangle,
FloatRect targetRect,
Color renderColor,
- GameRenderTexture? renderTarget = null,
+ IGameRenderTexture? renderTarget = null,
GameBlendModes blendMode = GameBlendModes.None,
GameShader? shader = null,
float rotationDegrees = 0.0f,
diff --git a/Intersect.Client.Core/Core/Input.cs b/Intersect.Client.Core/Core/Input.cs
index b1687064c0..1b0aec3b90 100644
--- a/Intersect.Client.Core/Core/Input.cs
+++ b/Intersect.Client.Core/Core/Input.cs
@@ -1,6 +1,4 @@
-using System.Diagnostics;
using Intersect.Admin.Actions;
-using Intersect.Client.Core.Controls;
using Intersect.Client.Entities;
using Intersect.Client.Framework.GenericClasses;
using Intersect.Client.Framework.Graphics;
@@ -13,7 +11,7 @@
using Intersect.Client.Networking;
using Intersect.Configuration;
using Intersect.Enums;
-using Intersect.Utilities;
+using Intersect.Framework.Core;
namespace Intersect.Client.Core;
@@ -146,7 +144,7 @@ public static void OnKeyPressed(Keys modifier, Keys key)
if (simplifiedEscapeMenuSetting)
{
- if (gameUi.EscapeMenu.IsVisible)
+ if (gameUi.EscapeMenu.IsVisibleInTree)
{
gameUi.EscapeMenu.ToggleHidden();
}
@@ -217,7 +215,7 @@ public static void OnKeyPressed(Keys modifier, Keys key)
}
case Control.OpenDebugger:
- _ = MutableInterface.ToggleDebug();
+ Interface.Interface.CurrentInterface.ToggleDebug();
break;
}
diff --git a/Intersect.Client.Core/Core/Main.cs b/Intersect.Client.Core/Core/Main.cs
index a727e85057..53921e2f56 100644
--- a/Intersect.Client.Core/Core/Main.cs
+++ b/Intersect.Client.Core/Core/Main.cs
@@ -4,9 +4,9 @@
using Intersect.Client.Networking;
using Intersect.Configuration;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.GameObjects;
using Intersect.GameObjects.Maps;
-using Intersect.Utilities;
// ReSharper disable All
@@ -106,7 +106,7 @@ private static void ProcessIntro()
{
if (ClientConfiguration.Instance.IntroImages.Count > 0)
{
- GameTexture imageTex = Globals.ContentManager.GetTexture(
+ IGameTexture imageTex = Globals.ContentManager.GetTexture(
Framework.Content.TextureType.Image, ClientConfiguration.Instance.IntroImages[Globals.IntroIndex]
);
@@ -243,7 +243,7 @@ private static void ProcessGame()
//Update Game Animations
if (_animationTimer < millisecondsNow)
{
- Globals.AnimFrame = (Globals.AnimFrame + 1) % 3;
+ Globals.AnimationFrame = (Globals.AnimationFrame + 1) % 3;
_animationTimer = millisecondsNow + 500;
}
diff --git a/Intersect.Client.Core/Core/Sounds/Sound.cs b/Intersect.Client.Core/Core/Sounds/Sound.cs
index 7fbd074173..05d80ac571 100644
--- a/Intersect.Client.Core/Core/Sounds/Sound.cs
+++ b/Intersect.Client.Core/Core/Sounds/Sound.cs
@@ -2,7 +2,7 @@
using Intersect.Client.Framework.Core.Sounds;
using Intersect.Client.Framework.File_Management;
using Intersect.Client.General;
-using Intersect.Utilities;
+using Intersect.Framework.Core;
namespace Intersect.Client.Core.Sounds;
diff --git a/Intersect.Client.Core/Entities/Animation.cs b/Intersect.Client.Core/Entities/Animation.cs
index 44f4d930a5..6ba80b9e1a 100644
--- a/Intersect.Client.Core/Entities/Animation.cs
+++ b/Intersect.Client.Core/Entities/Animation.cs
@@ -5,6 +5,7 @@
using Intersect.Client.Framework.Graphics;
using Intersect.Client.General;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.Framework.Core.GameObjects.Animations;
using Intersect.GameObjects;
using Intersect.GameObjects.Animations;
diff --git a/Intersect.Client.Core/Entities/ChatBubble.cs b/Intersect.Client.Core/Entities/ChatBubble.cs
index 617693e388..fb5f548fcd 100644
--- a/Intersect.Client.Core/Entities/ChatBubble.cs
+++ b/Intersect.Client.Core/Entities/ChatBubble.cs
@@ -4,13 +4,14 @@
using Intersect.Client.Framework.Graphics;
using Intersect.Client.Framework.Gwen.ControlInternal;
using Intersect.Client.General;
+using Intersect.Framework.Core;
using Intersect.Utilities;
namespace Intersect.Client.Entities;
public partial class ChatBubble
{
- private readonly GameTexture? mBubbleTex;
+ private readonly IGameTexture? mBubbleTex;
private readonly Entity? mOwner;
diff --git a/Intersect.Client.Core/Entities/Critter.cs b/Intersect.Client.Core/Entities/Critter.cs
index 77c919b18e..6e00af5b15 100644
--- a/Intersect.Client.Core/Entities/Critter.cs
+++ b/Intersect.Client.Core/Entities/Critter.cs
@@ -4,6 +4,7 @@
using Intersect.Client.General;
using Intersect.Client.Maps;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.GameObjects;
using Intersect.GameObjects.Maps;
using Intersect.Utilities;
diff --git a/Intersect.Client.Core/Entities/Dash.cs b/Intersect.Client.Core/Entities/Dash.cs
index 4ce82c4f43..5cead5526b 100644
--- a/Intersect.Client.Core/Entities/Dash.cs
+++ b/Intersect.Client.Core/Entities/Dash.cs
@@ -2,6 +2,7 @@
using Intersect.Client.Maps;
using Intersect.Core;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.Utilities;
using Microsoft.Extensions.Logging;
diff --git a/Intersect.Client.Core/Entities/Entity.cs b/Intersect.Client.Core/Entities/Entity.cs
index bfe8e0ad66..fa7e7f11b0 100644
--- a/Intersect.Client.Core/Entities/Entity.cs
+++ b/Intersect.Client.Core/Entities/Entity.cs
@@ -15,6 +15,7 @@
using Intersect.Client.Spells;
using Intersect.Core;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.Framework.Core.GameObjects.Animations;
using Intersect.GameObjects;
using Intersect.GameObjects.Animations;
@@ -192,13 +193,13 @@ public Guid SpellCast
IReadOnlyDictionary IEntity.Stats =>
Enum.GetValues().ToDictionary(stat => stat, stat => Stat[(int)stat]);
- public GameTexture? Texture { get; set; }
+ public IGameTexture? Texture { get; set; }
#region "Animation Textures and Timing"
public SpriteAnimations SpriteAnimation { get; set; } = SpriteAnimations.Normal;
- public Dictionary AnimatedTextures { get; set; } = [];
+ public Dictionary AnimatedTextures { get; set; } = [];
public int SpriteFrame { get; set; } = 0;
@@ -1379,7 +1380,7 @@ public virtual void DrawEquipment(string filename, Color renderColor)
}
// Paperdoll textures and Frames.
- GameTexture? paperdollTex = null;
+ IGameTexture? paperdollTex = null;
var spriteFrames = SpriteFrames;
// Extract filename without it's extension.
@@ -1537,7 +1538,7 @@ public void DrawLabels(
if (backgroundColor != Color.Transparent)
{
Graphics.DrawGameTexture(
- Graphics.Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Graphics.Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
new FloatRect(x - textSize.X / 2f - 4, y, textSize.X + 8, textSize.Y), backgroundColor
);
}
@@ -1600,7 +1601,7 @@ public virtual void DrawName(Color? textColor, Color? borderColor = null, Color?
if (backgroundColor != Color.Transparent)
{
Graphics.DrawGameTexture(
- Graphics.Renderer.GetWhiteTexture(), new FloatRect(0, 0, 1, 1),
+ Graphics.Renderer.WhitePixel, new FloatRect(0, 0, 1, 1),
new FloatRect(x - textSize.X / 2f - 4, y, textSize.X + 8, textSize.Y), backgroundColor
);
}
@@ -1727,7 +1728,7 @@ protected bool ShouldNotDrawHpBar
}
}
- public GameTexture GetBoundingHpBarTexture()
+ public IGameTexture GetBoundingHpBarTexture()
{
return GameTexture.GetBoundingTexture(
BoundsComparison.Height,
@@ -2196,7 +2197,7 @@ protected virtual void LoadAnimationTexture(string textureName, SpriteAnimations
}
}
- protected virtual bool TryGetAnimationTexture(string textureName, SpriteAnimations spriteAnimation, string textureOverride, out GameTexture texture)
+ protected virtual bool TryGetAnimationTexture(string textureName, SpriteAnimations spriteAnimation, string textureOverride, out IGameTexture texture)
{
var baseFilename = Path.GetFileNameWithoutExtension(textureName);
var extension = Path.GetExtension(textureName);
diff --git a/Intersect.Client.Core/Entities/Events/Event.cs b/Intersect.Client.Core/Entities/Events/Event.cs
index cf27dcb67a..c7606738d3 100644
--- a/Intersect.Client.Core/Entities/Events/Event.cs
+++ b/Intersect.Client.Core/Entities/Events/Event.cs
@@ -93,7 +93,7 @@ public override bool Update()
return success;
}
- protected bool TryEnsureTexture(out GameTexture? texture)
+ protected bool TryEnsureTexture(out IGameTexture? texture)
{
if (_drawCompletedWithoutTexture)
{
diff --git a/Intersect.Client.Core/Entities/Player.cs b/Intersect.Client.Core/Entities/Player.cs
index aec2d4672b..1e016f6463 100644
--- a/Intersect.Client.Core/Entities/Player.cs
+++ b/Intersect.Client.Core/Entities/Player.cs
@@ -22,6 +22,7 @@
using Intersect.Core;
using Intersect.Enums;
using Intersect.Extensions;
+using Intersect.Framework.Core;
using Intersect.Framework.Reflection;
using Intersect.GameObjects;
using Intersect.GameObjects.Maps;
@@ -504,7 +505,7 @@ private static int GetQuantityOfItemIn(IEnumerable items, Guid itemId)
return (int)Math.Min(count, int.MaxValue);
}
- public static int GetQuantityOfItemInBank(Guid itemId) => GetQuantityOfItemIn(Globals.Bank, itemId);
+ public static int GetQuantityOfItemInBank(Guid itemId) => GetQuantityOfItemIn(Globals.BankSlots, itemId);
public int GetQuantityOfItemInInventory(Guid itemId) => GetQuantityOfItemIn(Inventory, itemId);
@@ -852,7 +853,7 @@ public bool TryStoreItemInBank(
)
{
// Permission Check for Guild Bank
- if (Globals.GuildBank && !IsGuildBankDepositAllowed())
+ if (Globals.IsGuildBank && !IsGuildBankDepositAllowed())
{
ChatboxMsg.AddMessage(new ChatboxMsg(Strings.Guilds.NotAllowedDeposit.ToString(Globals.Me?.Guild), CustomColors.Alerts.Error, ChatMessageType.Bank));
return false;
@@ -876,7 +877,7 @@ public bool TryStoreItemInBank(
var sourceQuantity = GetQuantityOfItemInInventory(itemDescriptor.Id);
var quantity = quantityHint < 0 ? sourceQuantity : quantityHint;
- var targetSlots = Globals.Bank.ToArray();
+ var targetSlots = Globals.BankSlots.ToArray();
var movableQuantity = Item.FindSpaceForItem(
itemDescriptor.Id,
@@ -979,13 +980,13 @@ public bool TryRetrieveItemFromBank(
)
{
// Permission Check for Guild Bank
- if (Globals.GuildBank && !IsGuildBankWithdrawAllowed())
+ if (Globals.IsGuildBank && !IsGuildBankWithdrawAllowed())
{
ChatboxMsg.AddMessage(new ChatboxMsg(Strings.Guilds.NotAllowedWithdraw.ToString(Globals.Me?.Guild), CustomColors.Alerts.Error, ChatMessageType.Bank));
return false;
}
- slot ??= Globals.Bank[bankSlotIndex];
+ slot ??= Globals.BankSlots[bankSlotIndex];
if (!ItemBase.TryGet(slot.ItemId, out var itemDescriptor))
{
ApplicationContext.Context.Value?.Logger.LogWarning($"Tried to move item that does not exist from slot {bankSlotIndex}: {itemDescriptor.Id}");
@@ -1137,7 +1138,7 @@ private static void TryStoreItemInBagOnSubmit(Base sender, InputSubmissionEventA
public void TryRetrieveItemFromBag(int bagSlotIndex, int inventorySlotIndex)
{
- var bagSlot = Globals.Bag[bagSlotIndex];
+ var bagSlot = Globals.BagSlots[bagSlotIndex];
if (bagSlot == default)
{
return;
@@ -2704,7 +2705,7 @@ public virtual void DrawGuildName(Color textColor, Color? borderColor = default,
if (backgroundColor != Color.Transparent)
{
Graphics.DrawGameTexture(
- Graphics.Renderer.GetWhiteTexture(),
+ Graphics.Renderer.WhitePixel,
new FloatRect(0, 0, 1, 1),
new FloatRect(x - textSize.X / 2f - 4, y, textSize.X + 8, textSize.Y),
backgroundColor
diff --git a/Intersect.Client.Core/Entities/Projectiles/Projectile.cs b/Intersect.Client.Core/Entities/Projectiles/Projectile.cs
index fc92251d36..97eee67dd1 100644
--- a/Intersect.Client.Core/Entities/Projectiles/Projectile.cs
+++ b/Intersect.Client.Core/Entities/Projectiles/Projectile.cs
@@ -1,6 +1,7 @@
using Intersect.Client.Framework.Entities;
using Intersect.Client.General;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.GameObjects;
using Intersect.GameObjects.Maps;
using Intersect.Network.Packets.Server;
diff --git a/Intersect.Client.Core/Entities/Projectiles/ProjectileSpawns.cs b/Intersect.Client.Core/Entities/Projectiles/ProjectileSpawns.cs
index 79ab188e86..0bef4b6938 100644
--- a/Intersect.Client.Core/Entities/Projectiles/ProjectileSpawns.cs
+++ b/Intersect.Client.Core/Entities/Projectiles/ProjectileSpawns.cs
@@ -1,4 +1,5 @@
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.GameObjects;
using Intersect.Utilities;
diff --git a/Intersect.Client.Core/Entities/Status.cs b/Intersect.Client.Core/Entities/Status.cs
index 70c9659c17..9fa0dedf2a 100644
--- a/Intersect.Client.Core/Entities/Status.cs
+++ b/Intersect.Client.Core/Entities/Status.cs
@@ -1,5 +1,6 @@
using Intersect.Client.Framework.Entities;
using Intersect.Enums;
+using Intersect.Framework.Core;
using Intersect.Utilities;
namespace Intersect.Client.Entities;
diff --git a/Intersect.Client.Core/General/Globals.cs b/Intersect.Client.Core/General/Globals.cs
index a71215fd1e..a5238f4f00 100644
--- a/Intersect.Client.Core/General/Globals.cs
+++ b/Intersect.Client.Core/General/Globals.cs
@@ -15,59 +15,139 @@
namespace Intersect.Client.General;
-
public static partial class Globals
{
+ //Entities and stuff
+ //public static List Entities = new List();
+ public static readonly Dictionary Entities = [];
+
+ public static readonly List EntitiesToDispose = [];
+
+ //Control Objects
+ public static readonly List