This commit is contained in:
2025-12-17 11:42:57 +09:00
parent 3dba350fc2
commit d4764e304f
7 changed files with 246 additions and 1 deletions

View File

@@ -36,6 +36,11 @@ public class ComponentBase : MonoBehaviour,IPointerClickHandler
}
public virtual void getpath()
{
}
protected void FitCollider()
{
var renderers = GetComponentsInChildren<Renderer>();
@@ -58,5 +63,6 @@ public class ComponentBase : MonoBehaviour,IPointerClickHandler
{
onComponentClicked?.Invoke(componentType, data);
Debug.Log(componentType);
getpath();
}
}

View File

@@ -10,7 +10,7 @@ namespace Simulator.Data
{
public class SourceComponent : ComponentBase
{
SourceDataClass sourceData = new SourceDataClass();
public SourceDataClass sourceData = new SourceDataClass();
SourceModelData sourceModelData = new SourceModelData();
public List<GameObject> entitySocket;
List<Entity> possessEntities = new List<Entity>();
@@ -155,5 +155,12 @@ namespace Simulator.Data
// 텍스트 갱신
currentBubble.SetMessage(text);
}
public override void getpath()
{
PathIndexer.Build(ComponentsManager.Instance.logicDetailData);
var path = PathIndexer.GetNodePath(sourceData);
Debug.Log(path);
}
}
}

View File

@@ -0,0 +1,168 @@
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
public static class PathIndexer
{
private sealed class NodeMeta
{
public object Parent;
public string Segment; // 예: "production_system", "asrs[0]"
}
// 모델 클래스를 수정하지 않기 위해 외부 메타 테이블 사용
private static readonly ConditionalWeakTable<object, NodeMeta> _meta = new();
/// <summary>
/// 루트부터 전체 그래프를 순회하며 (Parent, Segment) 메타를 구축합니다.
/// 로직 데이터 로딩/디시리얼라이즈 직후 1회 호출을 권장합니다.
/// </summary>
public static void Build(object root)
{
if (root == null) throw new ArgumentNullException(nameof(root));
var visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
Visit(root, parent: null, segment: null, visited);
}
/// <summary>
/// 특정 객체 노드의 경로(노드까지)를 반환합니다. 예: "production_system.asrs[0]"
/// </summary>
public static string GetNodePath(object node, string rootPrefix = null)
{
if (node == null) throw new ArgumentNullException(nameof(node));
var segments = new List<string>();
var cur = node;
while (cur != null && _meta.TryGetValue(cur, out var m))
{
if (!string.IsNullOrWhiteSpace(m.Segment))
segments.Add(m.Segment);
cur = m.Parent;
}
segments.Reverse();
var path = string.Join(".", segments.Where(s => !string.IsNullOrWhiteSpace(s)));
if (!string.IsNullOrWhiteSpace(rootPrefix))
{
// 서버가 production_system[0] 같은 추가 프리픽스를 요구하면 여기서 붙이세요.
// 예: rootPrefix = "production_system[0]" 또는 "data" 등
path = string.IsNullOrWhiteSpace(path) ? rootPrefix : $"{rootPrefix}.{path}";
}
return path;
}
/// <summary>
/// (변경된 객체) + (변경된 프로퍼티명)으로 최종 patch path를 만듭니다.
/// 예: GetPropertyPath(asrs0, "name") => "production_system.asrs[0].name"
/// </summary>
public static string GetPropertyPath(object node, string propertyName, string rootPrefix = null)
{
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("propertyName is required.", nameof(propertyName));
var basePath = GetNodePath(node, rootPrefix);
return string.IsNullOrWhiteSpace(basePath) ? propertyName : $"{basePath}.{propertyName}";
}
// ----------------- 내부 구현 -----------------
private static void Visit(object node, object parent, string segment, HashSet<object> visited)
{
if (node == null) return;
var t = node.GetType();
if (IsTerminalType(t)) return;
if (!visited.Add(node)) return;
AttachMeta(node, parent, segment);
// 1) 프로퍼티 순회(기존)
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0);
foreach (var p in props)
{
object child;
try { child = p.GetValue(node); }
catch { continue; }
VisitChild(node, p.Name, child, visited);
}
// 2) 필드 순회(추가)
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(f => !f.IsStatic);
foreach (var f in fields)
{
object child;
try { child = f.GetValue(node); }
catch { continue; }
VisitChild(node, f.Name, child, visited);
}
}
private static void VisitChild(object parentNode, string memberName, object child, HashSet<object> visited)
{
if (child == null) return;
var ct = child.GetType();
if (IsTerminalType(ct)) return;
// IList면 원소에 memberName[i] 형태로 세그먼트 부여
if (child is IList list && child is not string)
{
for (int i = 0; i < list.Count; i++)
{
var elem = list[i];
if (elem == null) continue;
var et = elem.GetType();
if (IsTerminalType(et)) continue;
Visit(elem, parent: parentNode, segment: $"{memberName}[{i}]", visited);
}
return;
}
// 일반 객체
Visit(child, parent: parentNode, segment: memberName, visited);
}
private static void AttachMeta(object node, object parent, string segment)
{
// ConditionalWeakTable은 같은 키 add가 안 되므로 교체
_meta.Remove(node);
_meta.Add(node, new NodeMeta { Parent = parent, Segment = segment });
}
private static bool IsTerminalType(Type t)
{
if (t.IsPrimitive || t.IsEnum) return true;
if (t == typeof(string) || t == typeof(decimal)) return true;
if (t == typeof(DateTime) || t == typeof(DateTimeOffset) || t == typeof(Guid) || t == typeof(TimeSpan)) return true;
// Nullable<T> 처리
var nt = Nullable.GetUnderlyingType(t);
if (nt != null) return IsTerminalType(nt);
return false;
}
private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static readonly ReferenceEqualityComparer Instance = new();
public new bool Equals(object x, object y) => ReferenceEquals(x, y);
public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3198bec84bd9c4a459fde3c9ec54fae4

View File

@@ -1,4 +1,5 @@
using Simulator.Data;
using System;
using System.Collections.Generic;
using UnityEngine;
using UVC.UI.Window.PropertyWindow;
@@ -46,5 +47,17 @@ public class SimulatorProperty : MonoBehaviour
}
}
);
PropertyWindow.Instance.PropertyValueChanged += (sender, e) =>
{
PathIndexer.Build(ComponentsManager.Instance.logicDetailData);
var path = PathIndexer.GetNodePath(source);
Debug.Log($"{path}.{e.PropertyId}, type:{e.PropertyType}, newValue: {e.NewValue}");
Patch updateData = new Patch();
updateData.value = e.NewValue;
UpdateValueStack.AddPatch($"{path}.{e.PropertyId}", e.NewValue);
UpdateValueStack.Save();
//CursorManager.Instance.SetCursor(CursorType.HandPoint);
};
}
}

View File

@@ -0,0 +1,47 @@
using Newtonsoft.Json;
using NUnit.Framework.Constraints;
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class Patches
{
public List<Patch> patches=new List<Patch>();
}
[Serializable]
public class Patch
{
public string path;
public object value;
}
public class UpdateValueStack : MonoBehaviour
{
static Dictionary<string,object> patches=new Dictionary<string,object>();
public static void AddPatch(string path, object value)
{
if (patches.ContainsKey(path))
{
patches[path] = value;
}
else
{
patches.Add(path, value);
}
}
public static void Save()
{
Patches change = new Patches();
foreach (var patch in patches)
{
Patch pa = new Patch();
pa.path = patch.Key;
pa.value = patch.Value;
change.patches.Add(pa);
}
var data=JsonConvert.SerializeObject(change);
Debug.Log(data);
patches.Clear();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d6d985cb8a1e605499e1d47036464cb1