diff --git a/.idea/.idea.Fruitomation/.idea/.gitignore b/.idea/.idea.Fruitomation/.idea/.gitignore
new file mode 100644
index 0000000..cec0175
--- /dev/null
+++ b/.idea/.idea.Fruitomation/.idea/.gitignore
@@ -0,0 +1,15 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/.idea.Fruitomation.iml
+/projectSettingsUpdater.xml
+/contentModel.xml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/.idea.Fruitomation/.idea/encodings.xml b/.idea/.idea.Fruitomation/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.Fruitomation/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Fruitomation/.idea/indexLayout.xml b/.idea/.idea.Fruitomation/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.Fruitomation/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.Fruitomation/.idea/vcs.xml b/.idea/.idea.Fruitomation/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.Fruitomation/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/Pacore.meta b/Assets/Pacore.meta
new file mode 100644
index 0000000..c72ccdd
--- /dev/null
+++ b/Assets/Pacore.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8963d3aaf49fdce418a1779fdb04b311
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor.meta b/Assets/Pacore/Editor.meta
new file mode 100644
index 0000000..7a8103c
--- /dev/null
+++ b/Assets/Pacore/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2b0add55bcd6e65469713ef2482353e4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Caches.meta b/Assets/Pacore/Editor/Caches.meta
new file mode 100644
index 0000000..05e7656
--- /dev/null
+++ b/Assets/Pacore/Editor/Caches.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5feaa457922d235419f2e6d3f8f01c33
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs b/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs
new file mode 100644
index 0000000..1050d1d
--- /dev/null
+++ b/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs
@@ -0,0 +1,58 @@
+using PashaBibko.Pacore.Attributes;
+using System.Collections.Generic;
+using System.Reflection;
+using System;
+
+namespace PashaBibko.Pacore.Editor.Caches
+{
+ public static class InspectorCallableCache
+ {
+ public struct AttributeInfo
+ {
+ public InspectorCallableAttribute Attribute;
+ public MethodInfo AttachedMethod;
+ }
+
+ private const BindingFlags BINDING_FLAGS =
+ BindingFlags.Public |
+ BindingFlags.NonPublic |
+ BindingFlags.Instance;
+
+ private static Dictionary Cache { get; } = new();
+
+ public static AttributeInfo[] GetAllAttributesOfType(Type type)
+ {
+ /* Checks the cache for the type */
+ if (Cache.TryGetValue(type, out AttributeInfo[] attributes))
+ {
+ return attributes;
+ }
+
+ /* Finds all the functions with the attribute */
+ MethodInfo[] methods = type.GetMethods(BINDING_FLAGS);
+ List buttons = new();
+
+ foreach (MethodInfo method in methods)
+ {
+ InspectorCallableAttribute attribute
+ = method.GetCustomAttribute();
+
+ if (attribute != null)
+ {
+ AttributeInfo info = new()
+ {
+ Attribute = attribute,
+ AttachedMethod = method,
+ };
+
+ buttons.Add(info);
+ }
+ }
+
+ /* Adds it to the cache before returning */
+ AttributeInfo[] array = buttons.ToArray();
+ Cache.Add(type, array);
+ return array;
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs.meta b/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs.meta
new file mode 100644
index 0000000..9bd9de7
--- /dev/null
+++ b/Assets/Pacore/Editor/Caches/InspectorCallableCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 95392750cbc11ce4b9b9e6717c9e6588
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Caches/MonoScriptCache.cs b/Assets/Pacore/Editor/Caches/MonoScriptCache.cs
new file mode 100644
index 0000000..c72fb4c
--- /dev/null
+++ b/Assets/Pacore/Editor/Caches/MonoScriptCache.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using System;
+
+namespace PashaBibko.Pacore.Editor.Caches
+{
+ public static class MonoScriptCache
+ {
+ private static Dictionary Cache { get; } = new();
+
+ static MonoScriptCache()
+ {
+ /* Finds all MonoScripts and adds them to the dictionary by name */
+ MonoScript[] scripts = Resources.FindObjectsOfTypeAll();
+ foreach (MonoScript script in scripts)
+ {
+ Type scriptType = script.GetClass();
+ if (scriptType is not null)
+ {
+ string name = scriptType.FullName;
+ Cache.TryAdd(name, script);
+ }
+ }
+ }
+
+ public static MonoScript Get(string name)
+ {
+ /* Fetches the value (if there is one) without creating a default val */
+ Cache.TryGetValue(name, out MonoScript script);
+ return script;
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Caches/MonoScriptCache.cs.meta b/Assets/Pacore/Editor/Caches/MonoScriptCache.cs.meta
new file mode 100644
index 0000000..50b5221
--- /dev/null
+++ b/Assets/Pacore/Editor/Caches/MonoScriptCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 668c2ebd0824f1d4c9b324be7142823c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Drawers.meta b/Assets/Pacore/Editor/Drawers.meta
new file mode 100644
index 0000000..a3f5b92
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 47d063734da388944b54ed5a6ca386a7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs b/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs
new file mode 100644
index 0000000..34175a5
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs
@@ -0,0 +1,46 @@
+using PashaBibko.Pacore.Attributes;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+
+namespace PashaBibko.Pacore.Editor.Drawers
+{
+ [CustomPropertyDrawer(typeof(DetectInspectorChangesAttribute))]
+ public sealed class DetectInspectorChangesDrawer : PropertyDrawer
+ {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ /* Draws the property and checks for changes */
+ EditorGUI.BeginProperty(position, label, property);
+ EditorGUI.BeginChangeCheck();
+
+ EditorGUI.PropertyField(position, property, label);
+ if (EditorGUI.EndChangeCheck()) // Returns true if there were changes
+ {
+ property.serializedObject.ApplyModifiedProperties(); // Applies the changes
+ if (attribute is DetectInspectorChangesAttribute inspectorChangesAttribute)
+ {
+ const BindingFlags BINDING_FLAGS =
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
+
+ string methodName = inspectorChangesAttribute.ActionName;
+
+ /* Fetches the method and the object to call it on */
+ Object target = property.serializedObject.targetObject;
+ MethodInfo method = target.GetType().GetMethod(methodName, BINDING_FLAGS);
+ if (method == null)
+ {
+ Debug.LogError($"Method not found [{methodName}]");
+ }
+
+ else
+ {
+ method.Invoke(target, null);
+ }
+ }
+ }
+
+ EditorGUI.EndProperty();
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs.meta b/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs.meta
new file mode 100644
index 0000000..67479c3
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a5fad12cc06f1a749bc75948a24e6306
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs b/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs
new file mode 100644
index 0000000..377d202
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs
@@ -0,0 +1,23 @@
+using PashaBibko.Pacore.Attributes;
+using UnityEditor;
+using UnityEngine;
+
+namespace PashaBibko.Pacore.Editor.Drawers
+{
+ [CustomPropertyDrawer(typeof(InspectorReadOnlyAttribute))]
+ public sealed class InspectorReadOnlyDrawer : PropertyDrawer
+ {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ if (attribute is InspectorReadOnlyAttribute readOnlyAttribute)
+ {
+ GUI.enabled = false;
+
+ label.text = readOnlyAttribute.Name ?? label.text; // Uses custom name if it exists
+ EditorGUI.PropertyField(position, property, label);
+
+ GUI.enabled = true;
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs.meta b/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs.meta
new file mode 100644
index 0000000..692b8e8
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a90f608095c5b964fabd707859b141ed
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs b/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs
new file mode 100644
index 0000000..c60ea3c
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs
@@ -0,0 +1,42 @@
+using PashaBibko.Pacore.Editor.Caches;
+using UnityEditor;
+using UnityEngine;
+using System;
+using Object = UnityEngine.Object;
+
+namespace PashaBibko.Pacore.Editor.Drawers
+{
+ [CustomEditor(typeof(MonoBehaviour), editorForChildClasses: true)]
+ public class MonoBehaviourDrawer : UnityEditor.Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+ DrawFunctionButtons(target);
+ }
+
+ public static void DrawFunctionButtons(Object target)
+ {
+ Type type = target.GetType();
+ InspectorCallableCache.AttributeInfo[] buttons
+ = InspectorCallableCache.GetAllAttributesOfType(type);
+
+ if (buttons.Length == 0)
+ {
+ return;
+ }
+
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("Functions", EditorStyles.boldLabel);
+
+ foreach (InspectorCallableCache.AttributeInfo button in buttons)
+ {
+ string name = button.Attribute.ButtonName;
+ if (GUILayout.Button(name))
+ {
+ button.AttachedMethod.Invoke(target, null);
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs.meta b/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs.meta
new file mode 100644
index 0000000..47c95d4
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a0f42304568f6f54f9f7cf0c6e3e62ad
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs b/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs
new file mode 100644
index 0000000..f7c54a0
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs
@@ -0,0 +1,80 @@
+using PashaBibko.Pacore.Editor.Caches;
+using PashaBibko.Pacore.Attributes;
+using UnityEditor;
+using UnityEngine;
+using System;
+
+namespace PashaBibko.Pacore.Editor.Drawers
+{
+ [CustomPropertyDrawer(typeof(MonoScriptAttribute))]
+ public class MonoScriptDrawer : PropertyDrawer
+ {
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ /* I'm not sure if this will ever happen */
+ if (attribute is not MonoScriptAttribute attr)
+ {
+ return; // Stops NullReferenceExceptions
+ }
+
+ /* Makes sure the type is a string */
+ if (property.propertyType != SerializedPropertyType.String)
+ {
+ Debug.LogError($"Attribute [{nameof(MonoScriptAttribute)}] must be attached to a string");
+ return;
+ }
+
+ /* Draws the label of the script variable */
+ Rect rect = EditorGUI.PrefixLabel(position, label);
+
+ /* Fetches the script if there is a value assigned to the property */
+ string propValue = property.stringValue;
+ MonoScript script = null;
+ if (!string.IsNullOrEmpty(propValue))
+ {
+ script = MonoScriptCache.Get(propValue);
+ }
+
+ /* Draws the selected script and checks for changes */
+ script = EditorGUI.ObjectField(rect, script, typeof(MonoScript), allowSceneObjects: false) as MonoScript;
+ if (!GUI.changed)
+ {
+ return; // No changes to check
+ }
+
+ /* Makes sure the script is valid */
+ if (script is null)
+ {
+ property.stringValue = string.Empty; // No type means empty string
+ return;
+ }
+
+ /* Fetches the type of the attached script, and checks if it is valid */
+ Type type = script.GetClass();
+ if (type is null || (attr.MonoType is not null && !attr.MonoType.IsAssignableFrom(type)))
+ {
+ Debug.LogError($"The script file [{script.name}] doesn't contain an assignable MonoBehaviour class");
+ return;
+ }
+
+ /* If a forced inheritance has been set, checks its validity */
+ if (attr.InheritedFrom is not null && !attr.InheritedFrom.IsAssignableFrom(type))
+ {
+ property.stringValue = type.FullName; // Still applies the changes to make it appear like it is functioning
+
+ string attachedObject = property.serializedObject.targetObject.name;
+ string inherited = attr.InheritedFrom.FullName;
+
+ Debug.LogError
+ (
+ $"Field [{property.name}] as part of [{attachedObject}] is invalid.\n" +
+ $"The type must inherit from [{inherited}]. Currently it is [{type.FullName}]"
+ );
+ return;
+ }
+
+ /* Assigns the name of the type to the property so it can be created */
+ property.stringValue = type.FullName;
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs.meta b/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs.meta
new file mode 100644
index 0000000..75f0cef
--- /dev/null
+++ b/Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 21cdc55dcda1d9e40a1463929b020832
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/EditorWindows.meta b/Assets/Pacore/Editor/EditorWindows.meta
new file mode 100644
index 0000000..7ed21ac
--- /dev/null
+++ b/Assets/Pacore/Editor/EditorWindows.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 534297012cecc0a4892f3262f829f411
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs b/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs
new file mode 100644
index 0000000..43bd23e
--- /dev/null
+++ b/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using PashaBibko.Pacore.DevTools;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace Pacore.Editor.EditorWindows
+{
+ public class ProfilerWindow : EditorWindow
+ {
+ private double LastRepaint = double.MinValue;
+ private Vector2 ScrollPosition;
+
+ [MenuItem("Pacore/Profiler")]
+ public static void OpenWindow() => GetWindow();
+
+ /* Makes sure the window is repainted often to show latest info */
+ private void OnDisable() => EditorApplication.update -= CheckForRepaint;
+ private void OnEnable() => EditorApplication.update += CheckForRepaint;
+
+ private void CheckForRepaint()
+ {
+ /* Triggers a repaint when it has been over 1 second since last repaint */
+ double now = EditorApplication.timeSinceStartup;
+ if (now - LastRepaint > 1f)
+ {
+ LastRepaint = now;
+ Repaint();
+ }
+ }
+
+ private static void DrawEmptyProfiler()
+ {
+ GUILayout.BeginVertical(style: "box");
+
+ GUILayout.Label(text: "No profiler snippets found", EditorStyles.boldLabel);
+ GUILayout.Label(text: "Try running the game to collect profile samples");
+
+ GUILayout.EndVertical();
+ }
+
+ private static void DrawProfilerSnippet(string name, List times)
+ {
+ GUILayout.BeginVertical(style: "box");
+
+ GUILayout.BeginHorizontal();
+ GUILayout.Label(text: name, EditorStyles.boldLabel);
+ GUILayout.EndHorizontal();
+
+ float averageMs = times.Sum() / (float)times.Count;
+ GUILayout.Label("Average time: " + averageMs);
+
+ IGrouping[] ordered = times // List
+ .GroupBy(time => time) // IEnumerable>
+ .OrderByDescending(time => time.Key) // IOrdererEnumerable>
+ .ToArray();
+
+ foreach (IGrouping group in ordered)
+ {
+ string text = group.Count() > 1
+ ? $"- {group.Key}ms {group.Count()}x"
+ : $"- {group.Key}ms";
+
+ GUILayout.Label(text, EditorStyles.label);
+ }
+
+ GUILayout.EndVertical();
+ }
+
+ /* Draws the different snippets to the screen */
+ private void OnGUI()
+ {
+ GUILayout.Space(5); // Stops the window rendering right at the top
+ IReadOnlyDictionary> snippets = CodeProfiler.GetProfilerSnippets();
+
+ /* If there are no snippets, draws an inspector with instructions */
+ if (snippets.Count == 0)
+ {
+ DrawEmptyProfiler();
+ return;
+ }
+
+ /* Draws a quick overview of all snippets found */
+ GUILayout.BeginVertical(style: "box");
+ GUILayout.Label(text: $"[{snippets.Count}] different profiler snippets found");
+ GUILayout.EndVertical();
+
+ /* Draws each profiler snippet */
+ ScrollPosition = EditorGUILayout.BeginScrollView(ScrollPosition);
+ foreach (KeyValuePair> snippet in snippets)
+ {
+ DrawProfilerSnippet(snippet.Key, snippet.Value);
+ }
+ EditorGUILayout.EndScrollView();
+ }
+ }
+}
diff --git a/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs.meta b/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs.meta
new file mode 100644
index 0000000..dfe41a6
--- /dev/null
+++ b/Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a4aab8fc202fced4c8c8b1b346526ccb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Editor/Pacore.Editor.asmdef b/Assets/Pacore/Editor/Pacore.Editor.asmdef
new file mode 100644
index 0000000..4836a1a
--- /dev/null
+++ b/Assets/Pacore/Editor/Pacore.Editor.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Pacore.Editor",
+ "rootNamespace": "",
+ "references": [
+ "GUID:4d6a1c1727d80284f86640644e0ea451"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/Pacore/Editor/Pacore.Editor.asmdef.meta b/Assets/Pacore/Editor/Pacore.Editor.asmdef.meta
new file mode 100644
index 0000000..badacb3
--- /dev/null
+++ b/Assets/Pacore/Editor/Pacore.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8c0ae1ec093a9314e957ad337005e456
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime.meta b/Assets/Pacore/Runtime.meta
new file mode 100644
index 0000000..dfaca05
--- /dev/null
+++ b/Assets/Pacore/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 922281d628f2a4343942e99abdeaa283
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes.meta b/Assets/Pacore/Runtime/Attributes.meta
new file mode 100644
index 0000000..0c84640
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d31e288d186b4e249afbfe36c24abe04
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs b/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs
new file mode 100644
index 0000000..b895053
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs
@@ -0,0 +1,8 @@
+using UnityEngine.Scripting;
+using System;
+
+namespace PashaBibko.Pacore.Attributes
+{
+ [Preserve, AttributeUsage(AttributeTargets.Class)]
+ public class CreateInstanceOnStartAttribute : Attribute { }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs.meta b/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs.meta
new file mode 100644
index 0000000..1423d14
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b60c10877f46d5847919dd8f6d52d45a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs b/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs
new file mode 100644
index 0000000..5cf73e2
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs
@@ -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;
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs.meta b/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs.meta
new file mode 100644
index 0000000..7f49783
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2280212742639854c946c532ca692fb8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs b/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs
new file mode 100644
index 0000000..dcbd1d5
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs
@@ -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;
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs.meta b/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs.meta
new file mode 100644
index 0000000..df0815c
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/InspectorCallable.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7650b3a0488876b43ad6b8472def84e8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs b/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs
new file mode 100644
index 0000000..ac9a876
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs
@@ -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;
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs.meta b/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs.meta
new file mode 100644
index 0000000..782bff4
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d90a8dcab504ac04a9dd61f603bac921
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs b/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs
new file mode 100644
index 0000000..80e1d8b
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs
@@ -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;
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs.meta b/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs.meta
new file mode 100644
index 0000000..2bcd3bd
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 53c17374ddf436d4095f8d8eb12d77fb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs b/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs
new file mode 100644
index 0000000..38b3d37
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace PashaBibko.Pacore.Attributes
+{
+ [AttributeUsage(validOn: AttributeTargets.Field)]
+ public class StaticInspectorFieldAttribute : Attribute { }
+}
diff --git a/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs.meta b/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs.meta
new file mode 100644
index 0000000..a5dd2b9
--- /dev/null
+++ b/Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1017ebd6495c20049a2e9c9e747c8f67
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/ClassAttributeCache.cs b/Assets/Pacore/Runtime/ClassAttributeCache.cs
new file mode 100644
index 0000000..db8da39
--- /dev/null
+++ b/Assets/Pacore/Runtime/ClassAttributeCache.cs
@@ -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> AttributeCache { get; set; }
+
+ public static ReadOnlyCollection GetAttributesOf()
+ {
+ return AttributeCache.TryGetValue(typeof(T), out List 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
+ .Where(type => type.IsClass && !type.IsAbstract) // IEnumerable
+ .ToArray();
+
+ /* Allocates space for the cache */
+ AttributeCache = classes // Type[]
+ .Where(type => typeof(Attribute).IsAssignableFrom(type)) // IEnumerable
+ .ToDictionary
+ (
+ keySelector: t => t,
+ elementSelector: _ => new List()
+ ); // Dictionary>
+
+ /* Finds which attributes are attached to what classes */
+ HashSet 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);
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/ClassAttributeCache.cs.meta b/Assets/Pacore/Runtime/ClassAttributeCache.cs.meta
new file mode 100644
index 0000000..1e1d463
--- /dev/null
+++ b/Assets/Pacore/Runtime/ClassAttributeCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0106f79bf6ab1134187b81144763795b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs b/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs
new file mode 100644
index 0000000..03edfd9
--- /dev/null
+++ b/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs
@@ -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 types =
+ ClassAttributeCache.GetAttributesOf();
+
+ /* 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);
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs.meta b/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs.meta
new file mode 100644
index 0000000..7548de2
--- /dev/null
+++ b/Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6af2fc25cdc5ad34e82f33536ab03372
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/DevTools.meta b/Assets/Pacore/Runtime/DevTools.meta
new file mode 100644
index 0000000..fdfca04
--- /dev/null
+++ b/Assets/Pacore/Runtime/DevTools.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: daccdbc0b2ee6714e9e5ff4a1d398f76
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs b/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs
new file mode 100644
index 0000000..6892ff6
--- /dev/null
+++ b/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System;
+
+namespace PashaBibko.Pacore.DevTools
+{
+ public static class CodeProfiler
+ {
+ private static Dictionary> ProfilerSnippets { get; } = new();
+ public static IReadOnlyDictionary> GetProfilerSnippets() => ProfilerSnippets;
+
+ private static void AddProfileSnippet(string name, long ms)
+ {
+ if (!ProfilerSnippets.ContainsKey(name))
+ {
+ ProfilerSnippets.Add(name, new List());
+ }
+
+ 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);
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs.meta b/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs.meta
new file mode 100644
index 0000000..5fd29d0
--- /dev/null
+++ b/Assets/Pacore/Runtime/DevTools/CodeProfiler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8ede419ddf0f2c64f89935e3d36ebeb7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Pacore.asmdef b/Assets/Pacore/Runtime/Pacore.asmdef
new file mode 100644
index 0000000..8c723c7
--- /dev/null
+++ b/Assets/Pacore/Runtime/Pacore.asmdef
@@ -0,0 +1,14 @@
+{
+ "name": "Pacore",
+ "rootNamespace": "",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/Pacore/Runtime/Pacore.asmdef.meta b/Assets/Pacore/Runtime/Pacore.asmdef.meta
new file mode 100644
index 0000000..e29ac10
--- /dev/null
+++ b/Assets/Pacore/Runtime/Pacore.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4d6a1c1727d80284f86640644e0ea451
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Threading.meta b/Assets/Pacore/Runtime/Threading.meta
new file mode 100644
index 0000000..b2b76ae
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8dbfd5bc438e9d546bd24c9a0192cf12
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs
new file mode 100644
index 0000000..2bb646c
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs
@@ -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 MainThreadMultistepQueue { get; } = new();
+ private static ConcurrentQueue MainThreadImmediateQueue { get; } = new();
+ private static ConcurrentQueue 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
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta
new file mode 100644
index 0000000..197613e
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 95b6b9f402ffb8d47851420ac464f93b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs b/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs
new file mode 100644
index 0000000..8ae9416
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs
@@ -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");
+ }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs.meta b/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs.meta
new file mode 100644
index 0000000..30607ce
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 25d001fa0a9a7404c95304eab5106343
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs
new file mode 100644
index 0000000..c81f6b9
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs
@@ -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(); }
+ }
+ }
+}
diff --git a/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta
new file mode 100644
index 0000000..b14f0ed
--- /dev/null
+++ b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: edacac53aa5b21442a07379ce4087b92
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: