Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying default C# system impls by attribute #42

Merged
merged 5 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions EcsactCsharpSystemImpl.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions EcsactCsharpSystemImpl/Editor.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions EcsactCsharpSystemImpl/Editor/CsharpSystemImplSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using UnityEditor;
using UnityEngine;
using System.IO;

#nullable enable

namespace Ecsact.Editor {

public class CsharpSystemImplSettings : ScriptableObject {
private static CsharpSystemImplSettings? _instance;
public const string assetPath =
"Assets/Editor/EcsactCsharpSystemImplSettings.asset";

[Tooltip("The assembly that contains all the Ecsacts sytem impls")]
public UnityEditorInternal.AssemblyDefinitionAsset? systemImplsAssembly;

public static CsharpSystemImplSettings Get() {
if(_instance != null) {
return _instance;
}

_instance = AssetDatabase.LoadAssetAtPath<CsharpSystemImplSettings>(
assetPath
);
if(_instance == null) {
_instance = ScriptableObject.CreateInstance<CsharpSystemImplSettings>();
Directory.CreateDirectory(Path.GetDirectoryName(assetPath));
AssetDatabase.CreateAsset(_instance, assetPath);
AssetDatabase.SaveAssetIfDirty(_instance);
}

if(_instance == null) {
throw new global::System.Exception(
"Failed to load CsharpSystemImplSettings"
);
}

return _instance;
}

}

} // namespace Ecsact.Editor
11 changes: 11 additions & 0 deletions EcsactCsharpSystemImpl/Editor/CsharpSystemImplSettings.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

231 changes: 231 additions & 0 deletions EcsactCsharpSystemImpl/Editor/CsharpSystemImplSettingsEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
using UnityEditor;
using UnityEngine;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using EcsactInternal;

#nullable enable

// https://docs.unity3d.com/Manual/AssemblyDefinitionFileFormat.html
[System.Serializable]
struct UnityAssemblyDefinitionFile {
public bool allowUnsafeCode;
public bool autoReferenced;
public bool noEngineReferences;
public List<string> defineConstraints;
public List<string> includePlatforms;
public List<string> excludePlatforms;
public string name;
public List<string> optionalUnityReferences;
public bool overrideReferences;
public List<string> precompiledReferences;
public List<string> references;
}

namespace Ecsact.Editor {

[InitializeOnLoad]
[CustomEditor(typeof(CsharpSystemImplSettings))]
public class CsharpSystemImplSettingsEditor : UnityEditor.Editor {
const float maxWidthMethodDetails = 400f;
const float minWidthMethodDetails = 300f;
private static GUILayoutOption[] methodDetailsLayoutOptions = new[]{
GUILayout.MaxWidth(maxWidthMethodDetails),
GUILayout.MinWidth(minWidthMethodDetails),
};
private static List<global::System.Type>? systemLikeTypes;

static CsharpSystemImplSettingsEditor() {
EditorApplication.delayCall += () => {
InitializeDelayed();
};
}

private static void InitializeDelayed() {
var settings = CsharpSystemImplSettings.Get();
var runtimeSettings = EcsactRuntimeSettings.Get();
EnsureDefaultCsharpSystemImplsAssemblyName(settings, runtimeSettings);
}

private static void EnsureDefaultCsharpSystemImplsAssemblyName
( CsharpSystemImplSettings settings
, EcsactRuntimeSettings runtimeSettings
)
{
var newDefault = "";
if(settings.systemImplsAssembly != null) {
var asmDefJson = global::System.Text.Encoding.Default.GetString(
settings.systemImplsAssembly.bytes
);
var asmDef = JsonUtility.FromJson<UnityAssemblyDefinitionFile>(
asmDefJson
);

newDefault = asmDef.name;
} else {
newDefault = "";
}

if(runtimeSettings.defaultCsharpSystemImplsAssemblyName != newDefault) {
runtimeSettings.defaultCsharpSystemImplsAssemblyName = newDefault;
EditorUtility.SetDirty(runtimeSettings);
}
}

public override void OnInspectorGUI() {
var settings = (target as CsharpSystemImplSettings)!;

EditorGUI.BeginChangeCheck();
settings.systemImplsAssembly = EditorGUILayout.ObjectField(
obj: settings.systemImplsAssembly,
objType: typeof(UnityEditorInternal.AssemblyDefinitionAsset),
allowSceneObjects: false,
label: "Assembly"
) as UnityEditorInternal.AssemblyDefinitionAsset;

if(EditorGUI.EndChangeCheck()) {
EnsureDefaultCsharpSystemImplsAssemblyName(
settings,
EcsactRuntimeSettings.Get()
);
}

if(settings.systemImplsAssembly != null) {
var asmDefJson = global::System.Text.Encoding.Default.GetString(
settings.systemImplsAssembly.bytes
);
var asmDef = JsonUtility.FromJson<UnityAssemblyDefinitionFile>(
asmDefJson
);

if(!asmDef.noEngineReferences) {
EditorGUILayout.HelpBox(
$"The assembly definition {asmDef.name} has engine references " +
"enabled. Ecsact system implementations run on multiple threads " +
"and should not use engine apis that are not thread safe.",
MessageType.Warning,
wide: false
);
}

var assembly = Assembly.Load(asmDef.name);
if(assembly != null) {
var implDict = new Dictionary<int, List<MethodInfo>>();
foreach(var type in assembly.GetTypes()) {
foreach(var method in type.GetMethods()) {
var defaultSystemImplAttr =
method.GetCustomAttribute<Ecsact.DefaultSystemImplAttribute>();
if(defaultSystemImplAttr == null) continue;

var systemLikeId = defaultSystemImplAttr.systemLikeId;
if(!implDict.ContainsKey(systemLikeId)) {
implDict.Add(systemLikeId, new());
}

implDict[systemLikeId].Add(method);
}
}

if(systemLikeTypes == null) {
systemLikeTypes = Ecsact.Util.GetAllSystemLikeTypes().ToList();
}

if(implDict.Count == 0) {
EditorGUILayout.HelpBox(
$"No system implementations detected in assembly {asmDef.name}. " +
"Add the Ecsact.DefaultSystemImpl attribute to a public static " +
$"method found in the {asmDef.name} assembly.",
MessageType.Info,
wide: false
);
}

foreach(var systemLikeType in systemLikeTypes) {
var systemLikeId = Ecsact.Util.GetSystemID(systemLikeType);
var methods = implDict.GetValueOrDefault(systemLikeId, new());
DrawSystemImplDetail(systemLikeId, systemLikeType, methods);
}

} else {
EditorGUILayout.HelpBox(
$"Unable to load assembly definition by name: {asmDef.name}",
MessageType.Error,
wide: false
);
}
}
}

private string GetMethodFullName
( MethodInfo methodInfo
)
{
return methodInfo.DeclaringType.FullName + "." + methodInfo.Name;
}

private void DrawSystemImplMethodDetails
( MethodInfo methodInfo
)
{
EditorGUILayout.BeginHorizontal(methodDetailsLayoutOptions);

var style = new GUIStyle(EditorStyles.label);
style.richText = true;
var label = GetMethodFullName(methodInfo);
var errors = DefaultCsharpSystemImplsLoader.ValidateImplMethodInfo(
methodInfo
);

if(errors.Count > 0) {
label = $"<color=red>{label}</color>";
}

EditorGUILayout.LabelField(
label: new GUIContent(label, string.Join("\n", errors)),
options: new GUILayoutOption[]{},
style: style
);

EditorGUILayout.EndHorizontal();
}

private void DrawSystemImplDetail
( global::System.Int32 systemLikeId
, global::System.Type systemLikeType
, List<MethodInfo> methods
)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(
systemLikeType.FullName,
new GUILayoutOption[]{
GUILayout.ExpandWidth(true)
}
);

if(methods.Count == 0) {
EditorGUILayout.LabelField(
label: "(none)",
options: methodDetailsLayoutOptions
);
GUILayout.Space(10f);
} else if(methods.Count == 1) {
DrawSystemImplMethodDetails(methods[0]);
} else {
EditorGUILayout.BeginVertical(methodDetailsLayoutOptions);
foreach(var method in methods) {
DrawSystemImplMethodDetails(method);
}
EditorGUILayout.HelpBox(
"Multiple default system impls for the same system is not allowed.",
MessageType.Error,
wide: true
);
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndHorizontal();
}
}

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions EcsactCsharpSystemImpl/Editor/EcsactCsharpSystemImplEditor.asmdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "EcsactCsharpSystemImplEditor",
"rootNamespace": "",
"references": [
"GUID:2d10fa57d8150f7499b7579b289a41a2",
"GUID:6e7029456009ab94da19a8f93d5e3523"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions EcsactCsharpSystemImpl/Runtime.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading