From b3202a4636f865e6b64abf9fc36cfa7d02f7616b Mon Sep 17 00:00:00 2001 From: Pasha Date: Mon, 30 Mar 2026 10:32:44 +0100 Subject: [PATCH] Added Pacore --- .idea/.idea.Fruitomation/.idea/.gitignore | 15 +++ .idea/.idea.Fruitomation/.idea/encodings.xml | 4 + .../.idea.Fruitomation/.idea/indexLayout.xml | 8 ++ .idea/.idea.Fruitomation/.idea/vcs.xml | 6 + Assets/Pacore.meta | 8 ++ Assets/Pacore/Editor.meta | 8 ++ Assets/Pacore/Editor/Caches.meta | 8 ++ .../Editor/Caches/InspectorCallableCache.cs | 58 +++++++++ .../Caches/InspectorCallableCache.cs.meta | 11 ++ .../Pacore/Editor/Caches/MonoScriptCache.cs | 34 +++++ .../Editor/Caches/MonoScriptCache.cs.meta | 11 ++ Assets/Pacore/Editor/Drawers.meta | 8 ++ .../Drawers/DetectInspectorChangesDrawer.cs | 46 +++++++ .../DetectInspectorChangesDrawer.cs.meta | 11 ++ .../Editor/Drawers/InspectorReadOnlyDrawer.cs | 23 ++++ .../Drawers/InspectorReadOnlyDrawer.cs.meta | 11 ++ .../Editor/Drawers/MonoBehaviourDrawer.cs | 42 +++++++ .../Drawers/MonoBehaviourDrawer.cs.meta | 11 ++ .../Pacore/Editor/Drawers/MonoScriptDrawer.cs | 80 ++++++++++++ .../Editor/Drawers/MonoScriptDrawer.cs.meta | 11 ++ Assets/Pacore/Editor/EditorWindows.meta | 8 ++ .../Editor/EditorWindows/ProfilerWindow.cs | 97 ++++++++++++++ .../EditorWindows/ProfilerWindow.cs.meta | 11 ++ Assets/Pacore/Editor/Pacore.Editor.asmdef | 16 +++ .../Pacore/Editor/Pacore.Editor.asmdef.meta | 7 ++ Assets/Pacore/Runtime.meta | 8 ++ Assets/Pacore/Runtime/Attributes.meta | 8 ++ .../Attributes/CreateInstanceOnStart.cs | 8 ++ .../Attributes/CreateInstanceOnStart.cs.meta | 11 ++ .../Attributes/DetectInspectorChanges.cs | 16 +++ .../Attributes/DetectInspectorChanges.cs.meta | 11 ++ .../Runtime/Attributes/InspectorCallable.cs | 15 +++ .../Attributes/InspectorCallable.cs.meta | 11 ++ .../Runtime/Attributes/InspectorReadOnly.cs | 16 +++ .../Attributes/InspectorReadOnly.cs.meta | 11 ++ .../Runtime/Attributes/MonoScriptAttribute.cs | 24 ++++ .../Attributes/MonoScriptAttribute.cs.meta | 11 ++ .../Attributes/StaticInspectorField.cs | 7 ++ .../Attributes/StaticInspectorField.cs.meta | 11 ++ Assets/Pacore/Runtime/ClassAttributeCache.cs | 57 +++++++++ .../Runtime/ClassAttributeCache.cs.meta | 11 ++ .../Runtime/CreateInstanceOnStartLoader.cs | 33 +++++ .../CreateInstanceOnStartLoader.cs.meta | 11 ++ Assets/Pacore/Runtime/DevTools.meta | 8 ++ .../Pacore/Runtime/DevTools/CodeProfiler.cs | 42 +++++++ .../Runtime/DevTools/CodeProfiler.cs.meta | 11 ++ Assets/Pacore/Runtime/Pacore.asmdef | 14 +++ Assets/Pacore/Runtime/Pacore.asmdef.meta | 7 ++ Assets/Pacore/Runtime/Threading.meta | 8 ++ .../Runtime/Threading/ThreadDispatcher.cs | 118 ++++++++++++++++++ .../Threading/ThreadDispatcher.cs.meta | 11 ++ .../Runtime/Threading/ThreadEnforcer.cs | 44 +++++++ .../Runtime/Threading/ThreadEnforcer.cs.meta | 11 ++ .../Pacore/Runtime/Threading/ThreadSafeTry.cs | 24 ++++ .../Runtime/Threading/ThreadSafeTry.cs.meta | 11 ++ 55 files changed, 1142 insertions(+) create mode 100644 .idea/.idea.Fruitomation/.idea/.gitignore create mode 100644 .idea/.idea.Fruitomation/.idea/encodings.xml create mode 100644 .idea/.idea.Fruitomation/.idea/indexLayout.xml create mode 100644 .idea/.idea.Fruitomation/.idea/vcs.xml create mode 100644 Assets/Pacore.meta create mode 100644 Assets/Pacore/Editor.meta create mode 100644 Assets/Pacore/Editor/Caches.meta create mode 100644 Assets/Pacore/Editor/Caches/InspectorCallableCache.cs create mode 100644 Assets/Pacore/Editor/Caches/InspectorCallableCache.cs.meta create mode 100644 Assets/Pacore/Editor/Caches/MonoScriptCache.cs create mode 100644 Assets/Pacore/Editor/Caches/MonoScriptCache.cs.meta create mode 100644 Assets/Pacore/Editor/Drawers.meta create mode 100644 Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs create mode 100644 Assets/Pacore/Editor/Drawers/DetectInspectorChangesDrawer.cs.meta create mode 100644 Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs create mode 100644 Assets/Pacore/Editor/Drawers/InspectorReadOnlyDrawer.cs.meta create mode 100644 Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs create mode 100644 Assets/Pacore/Editor/Drawers/MonoBehaviourDrawer.cs.meta create mode 100644 Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs create mode 100644 Assets/Pacore/Editor/Drawers/MonoScriptDrawer.cs.meta create mode 100644 Assets/Pacore/Editor/EditorWindows.meta create mode 100644 Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs create mode 100644 Assets/Pacore/Editor/EditorWindows/ProfilerWindow.cs.meta create mode 100644 Assets/Pacore/Editor/Pacore.Editor.asmdef create mode 100644 Assets/Pacore/Editor/Pacore.Editor.asmdef.meta create mode 100644 Assets/Pacore/Runtime.meta create mode 100644 Assets/Pacore/Runtime/Attributes.meta create mode 100644 Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs create mode 100644 Assets/Pacore/Runtime/Attributes/CreateInstanceOnStart.cs.meta create mode 100644 Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs create mode 100644 Assets/Pacore/Runtime/Attributes/DetectInspectorChanges.cs.meta create mode 100644 Assets/Pacore/Runtime/Attributes/InspectorCallable.cs create mode 100644 Assets/Pacore/Runtime/Attributes/InspectorCallable.cs.meta create mode 100644 Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs create mode 100644 Assets/Pacore/Runtime/Attributes/InspectorReadOnly.cs.meta create mode 100644 Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs create mode 100644 Assets/Pacore/Runtime/Attributes/MonoScriptAttribute.cs.meta create mode 100644 Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs create mode 100644 Assets/Pacore/Runtime/Attributes/StaticInspectorField.cs.meta create mode 100644 Assets/Pacore/Runtime/ClassAttributeCache.cs create mode 100644 Assets/Pacore/Runtime/ClassAttributeCache.cs.meta create mode 100644 Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs create mode 100644 Assets/Pacore/Runtime/CreateInstanceOnStartLoader.cs.meta create mode 100644 Assets/Pacore/Runtime/DevTools.meta create mode 100644 Assets/Pacore/Runtime/DevTools/CodeProfiler.cs create mode 100644 Assets/Pacore/Runtime/DevTools/CodeProfiler.cs.meta create mode 100644 Assets/Pacore/Runtime/Pacore.asmdef create mode 100644 Assets/Pacore/Runtime/Pacore.asmdef.meta create mode 100644 Assets/Pacore/Runtime/Threading.meta create mode 100644 Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs create mode 100644 Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta create mode 100644 Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs create mode 100644 Assets/Pacore/Runtime/Threading/ThreadEnforcer.cs.meta create mode 100644 Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs create mode 100644 Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta 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: