Skip to content

Commit

Permalink
#173 port reference editor, sim connections, proxy cache for lists
Browse files Browse the repository at this point in the history
  • Loading branch information
katycat5e committed Jan 4, 2024
1 parent 656b4e1 commit 32f6d07
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 19 deletions.
13 changes: 12 additions & 1 deletion CCL.Creator/Editor/MethodButtonsDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ public override void OnGUI(Rect position, SerializedProperty targetProp, GUICont

if (GUI.Button(buttonRect, new GUIContent(button.Text, button.Tooltip ?? string.Empty)))
{
button.Action.Invoke(null, new object[] { targetProp.serializedObject.targetObject });
if (button.Action.IsStatic)
{
button.Action.Invoke(null, new object[] { targetProp.serializedObject.targetObject });
}
else
{
button.Action.Invoke(targetProp.serializedObject.targetObject, new object[0]);
}
}
}
}
Expand Down Expand Up @@ -65,6 +72,10 @@ private static IEnumerable<ButtonInfo> GetButtonsForField(SerializedProperty pro
{
return methodInfo;
}
else if (type.GetMethod(parts[1], ALL_INSTANCE) is MethodInfo instanceMethod)
{
return instanceMethod;
}
}
return null;
}
Expand Down
6 changes: 1 addition & 5 deletions CCL.Creator/Editor/PortIdEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,9 @@ private static IEnumerable<PortOption> GetPortsInObject(GameObject root)
{
foreach (var component in root.GetComponentsInChildren<SimComponentDefinitionProxy>())
{
//foreach (var field in component.GetType().GetFields())
foreach (var portDef in component.ExposedPorts)
{
//if ((field.FieldType == typeof(PortDefinition)) && (field.GetValue(component) is PortDefinition portDef))
//{
yield return new PortOption(root.name, component.ID, portDef.ID, portDef.type, portDef.valueType);
//}
yield return new PortOption(root.name, component.ID, portDef.ID, portDef.type, portDef.valueType);
}
}
}
Expand Down
136 changes: 136 additions & 0 deletions CCL.Creator/Editor/PortReferenceIdEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using CCL.Types.Proxies.Ports;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.SceneManagement;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace CCL.Creator.Editor
{
[CustomPropertyDrawer(typeof(PortReferenceIdAttribute))]
public class PortReferenceIdEditor : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
string? currentValue = property.stringValue;
if (string.IsNullOrEmpty(currentValue))
{
currentValue = null;
}

var component = property.serializedObject.targetObject;

EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

IEnumerable<GameObject> sources;

// otherwise we want to list ports on all parts of the car
string? prefabAssetPath = GetCurrentPrefabPath(component);
if (prefabAssetPath != null)
{
sources = GetSiblingPrefabs(prefabAssetPath);
}
else
{
var scene = EditorSceneManager.GetActiveScene();
sources = scene.GetRootGameObjects();
}

var options = GetPortOptions(sources);
int selected = options.FindIndex(p => p.ID == currentValue);

if ((selected < 0) && !string.IsNullOrEmpty(currentValue))
{
options.Add(new PortReferenceOption(currentValue));
selected = options.Count - 1;
}

string[] optionNames = options.Select(p => p.Description).ToArray();

int newIndex = EditorGUI.Popup(position, Math.Max(selected, 0), optionNames);

if (newIndex != selected)
{
property.stringValue = options[newIndex].ID;
}

EditorGUI.EndProperty();

property.serializedObject.ApplyModifiedProperties();
}

private static string? GetCurrentPrefabPath(UnityEngine.Object contextObj)
{
if (PrefabUtility.IsPartOfAnyPrefab(contextObj))
{
return PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(contextObj);
}

if (PrefabStageUtility.GetCurrentPrefabStage() is PrefabStage stage)
{
return stage.prefabAssetPath;
}

return null;
}

private static IEnumerable<GameObject> GetSiblingPrefabs(string prefabPath)
{
string curFolder = Path.GetDirectoryName(prefabPath);

return AssetDatabase.FindAssets("t:prefab", new string[] { curFolder })
.Select(AssetDatabase.GUIDToAssetPath)
.Where(p => p != null)
.Select(AssetDatabase.LoadAssetAtPath<GameObject>);
}

private static List<PortReferenceOption> GetPortOptions(IEnumerable<GameObject> sources)
{
var ids = new List<PortReferenceOption>
{
new PortReferenceOption(null, "Not Set")
};

foreach (var source in sources)
{
ids.AddRange(GetPortsInObject(source));
}
return ids;
}

private static IEnumerable<PortReferenceOption> GetPortsInObject(GameObject root)
{
foreach (var component in root.GetComponentsInChildren<SimComponentDefinitionProxy>())
{
foreach (var portDef in component.ExposedPortReferences)
{
yield return new PortReferenceOption(root.name, component.ID, portDef.ID);
}
}
}

private readonly struct PortReferenceOption
{
public readonly string PrefabName;
public readonly string? ID;

public PortReferenceOption(string prefabName, string compId, string portId)
{
PrefabName = prefabName;
ID = $"{compId}.{portId}";
}

public PortReferenceOption(string? fullId, string prefabName = "Unknown")
{
PrefabName = prefabName;
ID = fullId;
}

public string Description => $"{ID} ({PrefabName})";
}
}
}
2 changes: 2 additions & 0 deletions CCL.Importer/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ private static void Configure(IMapperConfigurationExpression cfg)
.ForMember(d => d.disableSqrDistance, o => o.MapFrom(d => d.disableDistance * d.disableDistance))
.ForMember(s => s.scriptsToDisable, o => o.MapFrom(s => s.scriptsToDisable.Select(x => GetFromCache(x) ?? x)));
}

public static bool ReplaceAll<T>(T _) => true;

public static void StoreComponentsInChildrenInCache<TSource, TDestination>(this GameObject prefab, System.Func<TSource, bool> canReplace)
where TSource : MonoBehaviour
Expand Down
21 changes: 10 additions & 11 deletions CCL.Importer/Processing/SimulationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,27 @@ public override void ExecuteStep(ModelProcessor context)

// If we have something that gets referenced through the simConnections decoupling mechanism - these are generally things
// that make ports exist.
var hasSimConnections = livery.prefab.GetComponentsInChildren<SimComponentDefinition>().Length > 0 ||
livery.prefab.GetComponentsInChildren<Connection>().Length > 0 ||
livery.prefab.GetComponentsInChildren<PortReferenceConnection>().Length > 0;
if (hasSimConnections)
var simConnections = livery.prefab.GetComponentInChildren<SimConnectionDefinition>(true);

if (!simConnections && (livery.prefab.GetComponentsInChildren<SimComponentDefinition>(true).Length > 0))
{
AttachSimConnectionsToPrefab(livery.prefab);
}

// If we have something that can use a sim controller and don't already have a sim controller
var needsSimController = livery.prefab.GetComponentInChildren<SimConnectionDefinition>() ||
livery.prefab.GetComponentsInChildren<ASimInitializedController>().Length > 0 &&
!livery.prefab.GetComponentInChildren<SimController>();
var needsSimController = livery.prefab.GetComponentInChildren<SimConnectionDefinition>(true) ||
livery.prefab.GetComponentsInChildren<ASimInitializedController>(true).Length > 0 &&
!livery.prefab.GetComponentInChildren<SimController>(true);
if (needsSimController)
{
var simController = livery.prefab.AddComponent<SimController>();
simController.connectionsDefinition = livery.prefab.GetComponent<SimConnectionDefinition>() ?? AttachSimConnectionsToPrefab(livery.prefab);
simController.otherSimControllers = livery.prefab.GetComponentsInChildren<ASimInitializedController>();
simController.connectionsDefinition = livery.prefab.GetComponentInChildren<SimConnectionDefinition>(true) ?? AttachSimConnectionsToPrefab(livery.prefab);
simController.otherSimControllers = livery.prefab.GetComponentsInChildren<ASimInitializedController>(true);
}

// In the event we have a sim controller and *not* a damage controller, we need to add a dummy damage controller
var needsDamageController = livery.prefab.GetComponentInChildren<SimController>() &&
!livery.prefab.GetComponentInChildren<DamageController>();
var needsDamageController = livery.prefab.GetComponentInChildren<SimController>(true) &&
!livery.prefab.GetComponentInChildren<DamageController>(true);
if (needsDamageController)
{
AttachDummyDamageController(livery.prefab);
Expand Down
37 changes: 37 additions & 0 deletions CCL.Importer/Proxies/Controllers/SimConnectionsReplacer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using AutoMapper;
using CCL.Types.Proxies.Ports;
using LocoSim.Definitions;
using System.ComponentModel.Composition;
using UnityEngine;

namespace CCL.Importer.Proxies.Controllers
{
[Export(typeof(IProxyReplacer))]
public class SimConnectionsReplacer : Profile, IProxyReplacer
{
public SimConnectionsReplacer()
{
CreateMap<SimConnectionsDefinitionProxy, SimConnectionDefinition>()
.ForMember("executionOrder", o => o.ConvertUsing(new CacheConverter(o.DestinationMember), "executionOrder"));

CreateMap<PortConnectionProxy, Connection>();
CreateMap<PortReferenceConnectionProxy, PortReferenceConnection>();
}

public void CacheAndReplaceProxies(GameObject prefab)
{
prefab.StoreComponentsInChildrenInCache<SimConnectionsDefinitionProxy, SimConnectionDefinition>(_ => true);

}

public void MapProxies(GameObject prefab)
{

}

public void ReplaceProxiesUncached(GameObject prefab)
{
prefab.ConvertFromCache(typeof(SimConnectionsDefinitionProxy), typeof(SimConnectionDefinition), _ => true);
}
}
}
58 changes: 56 additions & 2 deletions CCL.Importer/Proxies/ProxyAutomapperProfile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using AutoMapper;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -18,7 +19,7 @@ public ProxyAutomapperProfile() {
foreach(var cacheMapField in attribute.FieldsFromCache)
{
map.ForMember(cacheMapField, cfg => {
cfg.ConvertUsing(typeof(CacheConverter), cacheMapField);
cfg.ConvertUsing(new CacheConverter(cfg.DestinationMember), cacheMapField);
});
}
}
Expand All @@ -32,15 +33,68 @@ from a in t.GetCustomAttributes<ProxyMapAttribute>()
}
public class CacheConverter : IValueConverter<object, object>
{
private readonly Type _destType;

public CacheConverter(MemberInfo destMember)
{
if (destMember is FieldInfo field)
{
_destType = field.FieldType;
}
else if (destMember is PropertyInfo property)
{
_destType = property.PropertyType;
}
else throw new ArgumentException("Value converter used for non value member");
}

public object Convert(object sourceMember, ResolutionContext context)
{
if (sourceMember == null) return null!;

// check if T[]
if (sourceMember is Array sourceArr)
{
Type targetElemType = _destType.GetElementType();
Array destArray = Array.CreateInstance(targetElemType, sourceArr.Length);
ConvertArrayValues(sourceArr, destArray);
return destArray;
}

// check if List<T>
Type sourceType = sourceMember.GetType();
if (sourceType.IsConstructedGenericType && (typeof(List<>) == sourceType.GetGenericTypeDefinition()))
{
var destListType = typeof(List<>).MakeGenericType(_destType.GenericTypeArguments);
var destList = (IList)Activator.CreateInstance(destListType);
ConvertArrayValues((IList)sourceMember, destList);
return destList;
}

// otherwise, single script
if (!typeof(MonoBehaviour).IsInstanceOfType(sourceMember))
{
CCLPlugin.Warning("Attempted to map a non-monobehaviour");
return sourceMember;
}
var result = Mapper.GetFromCache(sourceMember as MonoBehaviour);
var result = Mapper.GetFromCache((MonoBehaviour)sourceMember);
return result;
}

private static void ConvertArrayValues(IList source, IList dest)
{
for (int i = 0; i < source.Count; i++)
{
if (!typeof(MonoBehaviour).IsInstanceOfType(source[i]))
{
CCLPlugin.Warning("Attempted to map a non-monobehaviour");
dest[i] = null;
}
else
{
dest[i] = Mapper.GetFromCache((MonoBehaviour)source[i]);
}
}
}
}
}
8 changes: 8 additions & 0 deletions CCL.Types/MethodButtonAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ public class MethodButtonAttribute : Attribute
public readonly string TextOverride;
public readonly string? Tooltip;

/// <summary>Render a button that calls the given method</summary>
/// <param name="methodName">Fully qualified path of the target method, in "Namespace.Class:Method" format.
/// <para>Must be a static method that takes the enclosing class as an argument, or a 0 parameter instance method on the enclosing class.</para>
/// </param>
public MethodButtonAttribute(string methodName, string buttonText)
{
MethodName = methodName;
TextOverride = buttonText;
}

/// <summary>Render a button that calls the given method, with a custom tooltip</summary>
/// <param name="methodName">Fully qualified path of the target method, in "Namespace.Class:Method" format.
/// <para>Must be a static method that takes the enclosing class as an argument, or a 0 parameter instance method on the enclosing class.</para>
/// </param>
public MethodButtonAttribute(string methodName, string buttonText, string tooltip) : this(methodName, buttonText)
{
Tooltip = tooltip;
Expand Down
21 changes: 21 additions & 0 deletions CCL.Types/Proxies/Ports/PortReferenceDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace CCL.Types.Proxies.Ports
{
[Serializable]
public class PortReferenceDefinition
{
public DVPortValueType valueType;

public string ID;

public bool writeAllowed;

public PortReferenceDefinition(DVPortValueType valueType, string iD, bool writeAllowed = false)
{
this.valueType = valueType;
this.writeAllowed = writeAllowed;
ID = iD;
}
}
}
Loading

0 comments on commit 32f6d07

Please sign in to comment.