Added Pacore

This commit is contained in:
2026-03-30 10:32:44 +01:00
parent 62f6553986
commit b3202a4636
55 changed files with 1142 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d31e288d186b4e249afbfe36c24abe04
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
using UnityEngine.Scripting;
using System;
namespace PashaBibko.Pacore.Attributes
{
[Preserve, AttributeUsage(AttributeTargets.Class)]
public class CreateInstanceOnStartAttribute : Attribute { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b60c10877f46d5847919dd8f6d52d45a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using UnityEngine;
using System;
namespace PashaBibko.Pacore.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Field)]
public sealed class DetectInspectorChangesAttribute : PropertyAttribute
{
public string ActionName { get; }
public DetectInspectorChangesAttribute(string function)
{
ActionName = function;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2280212742639854c946c532ca692fb8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System;
namespace PashaBibko.Pacore.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class InspectorCallableAttribute : Attribute
{
public string ButtonName { get; }
public InspectorCallableAttribute(string name)
{
ButtonName = name;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7650b3a0488876b43ad6b8472def84e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using UnityEngine;
using System;
namespace PashaBibko.Pacore.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Field)]
public sealed class InspectorReadOnlyAttribute : PropertyAttribute
{
public string Name { get; }
public InspectorReadOnlyAttribute(string name = null)
{
Name = name;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d90a8dcab504ac04a9dd61f603bac921
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using UnityEngine;
using System;
namespace PashaBibko.Pacore.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class MonoScriptAttribute : PropertyAttribute
{
public Type InheritedFrom { get; }
public Type MonoType { get; }
public MonoScriptAttribute(Type inherited, Type type = null)
{
InheritedFrom = inherited;
MonoType = type;
}
public MonoScriptAttribute(Type type = null)
{
InheritedFrom = null;
MonoType = type;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53c17374ddf436d4095f8d8eb12d77fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
using System;
namespace PashaBibko.Pacore.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Field)]
public class StaticInspectorFieldAttribute : Attribute { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1017ebd6495c20049a2e9c9e747c8f67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System;
using UnityEngine;
namespace PashaBibko.Pacore
{
public static class ClassAttributeCache
{
private static Dictionary<Type, List<Type>> AttributeCache { get; set; }
public static ReadOnlyCollection<Type> GetAttributesOf<T>()
{
return AttributeCache.TryGetValue(typeof(T), out List<Type> classes)
? classes.AsReadOnly()
: throw new ArgumentException($"Attribute [{nameof(T)}] is not used by any classes");
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
private static void ScanAllAssemblies()
{
/* Fetches all the class types in all loaded assemblies */
Type[] classes = AppDomain.CurrentDomain.GetAssemblies() // Assembly[]
.SelectMany(assembly => assembly.GetTypes()) // IEnumerable<Type>
.Where(type => type.IsClass && !type.IsAbstract) // IEnumerable<Type>
.ToArray();
/* Allocates space for the cache */
AttributeCache = classes // Type[]
.Where(type => typeof(Attribute).IsAssignableFrom(type)) // IEnumerable<Type>
.ToDictionary
(
keySelector: t => t,
elementSelector: _ => new List<Type>()
); // Dictionary<Type, List<Type>>
/* Finds which attributes are attached to what classes */
HashSet<Type> seen = new();
foreach (Type current in classes)
{
/* Tracks the seen attributes */
seen.Clear();
foreach (object attr in current.GetCustomAttributes(inherit: true))
{
seen.Add(attr.GetType());
}
/* Adds the class type to each attribute in the dictionary */
foreach (Type type in seen)
{
AttributeCache[type].Add(current);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0106f79bf6ab1134187b81144763795b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using PashaBibko.Pacore.Attributes;
using System.Collections.ObjectModel;
using UnityEngine;
using System;
using Object = UnityEngine.Object;
namespace PashaBibko.Pacore
{
public static class CreateInstanceOnStartLoader
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void CreateAllInstances()
{
/* Fetches all the types with the CreateInstanceOnStart attribute */
ReadOnlyCollection<Type> types =
ClassAttributeCache.GetAttributesOf<CreateInstanceOnStartAttribute>();
/* Creates a holder for all the other game objects to not clutter inspector */
GameObject holder = new("ScriptSpawnedInstances");
Object.DontDestroyOnLoad(holder); // Stops all the game objects from being destroyed
Transform parent = holder.transform;
/* Creates all the game objects for the selected types */
foreach (Type type in types)
{
GameObject go = new(type.Name, type);
go.transform.SetParent(parent);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6af2fc25cdc5ad34e82f33536ab03372
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: daccdbc0b2ee6714e9e5ff4a1d398f76
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Diagnostics;
using System;
namespace PashaBibko.Pacore.DevTools
{
public static class CodeProfiler
{
private static Dictionary<string, List<long>> ProfilerSnippets { get; } = new();
public static IReadOnlyDictionary<string, List<long>> GetProfilerSnippets() => ProfilerSnippets;
private static void AddProfileSnippet(string name, long ms)
{
if (!ProfilerSnippets.ContainsKey(name))
{
ProfilerSnippets.Add(name, new List<long>());
}
ProfilerSnippets[name].Add(ms);
}
public static ProfilerSnippetHandle Start(string name) => new(name);
public class ProfilerSnippetHandle : IDisposable
{
private Stopwatch Stopwatch { get; }
private string SnippetName { get; }
public ProfilerSnippetHandle(string name)
{
Stopwatch = Stopwatch.StartNew();
SnippetName = name;
}
public void Dispose()
{
Stopwatch.Stop();
AddProfileSnippet(SnippetName, Stopwatch.ElapsedMilliseconds);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ede419ddf0f2c64f89935e3d36ebeb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
{
"name": "Pacore",
"rootNamespace": "",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4d6a1c1727d80284f86640644e0ea451
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8dbfd5bc438e9d546bd24c9a0192cf12
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,118 @@
using System.Collections.Concurrent;
using PashaBibko.Pacore.Attributes;
using UnityEngine;
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Debug = UnityEngine.Debug;
namespace PashaBibko.Pacore.Threading
{
[CreateInstanceOnStart] public class ThreadDispatcher : MonoBehaviour
{
private static ConcurrentQueue<IEnumerator> MainThreadMultistepQueue { get; } = new();
private static ConcurrentQueue<Action> MainThreadImmediateQueue { get; } = new();
private static ConcurrentQueue<Action> BackgroundQueue { get; } = new();
private static SemaphoreSlim Semaphore { get; } = new(initialCount: 4);
private static int IsBackgroundProcessing; // Pseudo boolean
private static IEnumerator ActiveMultistepRoutine { get; set; }
private static ThreadDispatcher Instance;
private const long MAIN_THREAD_MS_MAX = 5;
private void Awake()
{
/* Makes sure there is only one instance */
if (Instance is not null)
{
Debug.LogError($"Cannot have multiple instances of [{nameof(ThreadDispatcher)}]");
return;
}
Instance = this;
}
private void OnDestroy()
{
Instance = null; // Allows the Dispatcher to be destroyed
}
public static void QueueMultistep(IEnumerator routine) => MainThreadMultistepQueue.Enqueue(routine);
public static void QueueImmediate(Action action) => MainThreadImmediateQueue.Enqueue(action);
public static void QueueBackground(Action action)
{
/* Adds to the queue and runs if there are no active threads */
BackgroundQueue.Enqueue(action);
TriggerBackgroundProcessing();
}
private static void TriggerBackgroundProcessing()
{
/* Makes sure there are not too many threads queued */
if (Interlocked.Exchange(ref IsBackgroundProcessing, 1) != 1)
{
Task.Run(ProcessBackgroundQueue);
}
}
private static async Task ProcessBackgroundQueue()
{
/* Empties the queue of all tasks */
while (BackgroundQueue.TryDequeue(out Action task))
{
await Semaphore.WaitAsync();
_ = Task.Run(() =>
{
ThreadSafe.Try
(
action: task,
final: () => Semaphore.Release()
);
});
}
/* Cleans up to allow for future procession */
Interlocked.Exchange(ref IsBackgroundProcessing, 0);
if (!BackgroundQueue.IsEmpty) // Items may be queued during cleanup
{
TriggerBackgroundProcessing();
}
}
private void Update()
{
/* Runs the Actions in the immediate queue */
Stopwatch sw = Stopwatch.StartNew(); // Used to make sure not too much processing is done in one go
while (MainThreadImmediateQueue.TryDequeue(out Action current) && sw.ElapsedMilliseconds < MAIN_THREAD_MS_MAX)
{
current.Invoke();
}
/* Runs the multistep actions (if there is still time) */
while (sw.ElapsedMilliseconds < MAIN_THREAD_MS_MAX)
{
/* Gets a new routine if there is none active */
if (ActiveMultistepRoutine == null)
{
if (!MainThreadMultistepQueue.TryDequeue(out IEnumerator next))
{
return; // There is none left so we can return early
}
ActiveMultistepRoutine = next;
}
/* Runs the next step in the routine */
if (!ActiveMultistepRoutine.MoveNext())
{
ActiveMultistepRoutine = null; // [MoveNext() -> false] means the routine has ended
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95b6b9f402ffb8d47851420ac464f93b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;
using System;
namespace PashaBibko.Pacore.Threading
{
public static partial class ThreadSafe
{
private static SynchronizationContext MainThreadContext { get; set; }
public class IncorrectThreadException : Exception
{
public IncorrectThreadException(string message)
: base(message)
{
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
private static void CaptureMainThreadContext()
{
MainThreadContext = SynchronizationContext.Current;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnforceMainThread()
{
if (SynchronizationContext.Current != MainThreadContext)
{
throw new IncorrectThreadException("Main thread function was called on a background thread");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnforceBackgroundThread()
{
if (SynchronizationContext.Current == MainThreadContext)
{
throw new IncorrectThreadException("Background thread function was called on the main thread");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25d001fa0a9a7404c95304eab5106343
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System.Runtime.CompilerServices;
using UnityEngine;
using System;
namespace PashaBibko.Pacore.Threading
{
public static partial class ThreadSafe
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Try(Action action, Action final = null)
{
try { action(); }
/* Makes sure any exceptions are caught and logged properly */
catch (Exception ex)
{
ThreadDispatcher.QueueImmediate(() => Debug.Log($"Exception: [{ex.Message}]"));
throw;
}
finally { final?.Invoke(); }
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edacac53aa5b21442a07379ce4087b92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: