Files
XRLib/Assets/Scripts/Simulator/UI/PropertyWindow/ProcessorProperty.cs
2026-03-17 10:52:33 +09:00

659 lines
24 KiB
C#

using Simulator.Data;
using System;
using System.Collections.Generic;
using UnityEngine;
using UVC.UI.Window.PropertyWindow;
public class ProcessorProperty : MonoBehaviour
{
[SerializeField]
private PropertyWindow propertyWindow;
ProcessorDataClass processor;
private void Awake()
{
propertyWindow.PropertyValueChanged += OnPropertyValueChanged;
}
public void SetPropertyWindow(ComponentType type, ComponentDataBase data)
{
if (PlayerPropertyDataBase.isPlaying)
{
return;
}
switch (type)
{
case ComponentType.Processor:
processor = data as ProcessorDataClass;
InitProcessorProperty(data as ProcessorDataClass);
break;
}
}
void SaveChange(object source, object value, string name)
{
var path = PathIndexer.GetNodePath(source);
Patch updateData = new Patch();
updateData.value = value;
UpdateValueStack.AddPatch($"{path}.{name}", value);
}
public void InitProcessorProperty(ProcessorDataClass processor)
{
var entries = new List<IPropertyEntry>();
entries.Add(new StringProperty("name", "이름", processor.name) { IsReadOnly = true });
entries.Add(
new StringProperty("label", "라벨", processor.label) { IsReadOnly = false }
.Bind(setter: v => { processor.label = v; SaveChange(processor, v, "label"); })
);
entries.Add(CreatePositionGroup(processor));
entries.Add(
new ListProperty("model_type", "모델 타입",
new List<string>() { "processor_workbench", "processor_worker", "processor_conveyor_port", "processor_conveyor_roller" }, processor.model_type)
{ IsReadOnly = false }
.Bind(setter: v => { processor.model_type = v; SaveChange(processor, v, "model_type"); })
);
entries.Add(
new ListProperty("processing_type", "처리 유형",
new List<string>() { "combine", "seperate", "transform" }, processor.processor_type)
{ IsReadOnly = false }
.Bind(setter: v => { processor.processor_type = v; SaveChange(processor, v, "processing_type"); })
);
entries.Add(
new ListProperty("input_selection_policy", "투입 정책",
new List<string>() { "round_robin", "random" }, processor.input_selection_policy)
{ IsReadOnly = false }
.Bind(setter: v => { processor.input_selection_policy = v; SaveChange(processor, v, "input_selection_policy"); })
);
entries.Add(CreateInputPrefabToggleGroup(processor));
foreach (var prefab in PrefabManager.Instance.prefabDict.Keys)
entries.Add(CreateInputPrefabDetailGroup(processor, prefab));
entries.Add(
new ListProperty("output_selection_policy", "출고 정책",
new List<string>() { "round_robin", "random" }, processor.output_selection_policy)
{ IsReadOnly = false }
.Bind(setter: v => { processor.output_selection_policy = v; SaveChange(processor, v, "output_selection_policy"); })
);
entries.Add(CreateOutputPrefabToggleGroup(processor));
foreach (var prefab in PrefabManager.Instance.prefabDict.Keys)
entries.Add(CreateOutputPrefabDetailGroup(processor, prefab));
entries.AddRange(PolicyPropertyBuilder.CreatePolicyEntries(
"ProcessingTimePolicy", "처리 시간 정책", "display_mode_ProcessingTimePolicy",
() => processor.processing_time_policy, v => processor.processing_time_policy = v,
"processing_time_policy", processor, SaveChange));
entries.Add(BuildOutputTagChanges());
entries.Add(new ListProperty("outputTagType_string", "", new List<string> { "set" }, "set"));
entries.Add(new StringProperty("outputTagValue_string", "", ""));
entries.Add(new ListProperty("outputTagType_number", "", new List<string> { "set", "increment", "decrement" }, "값 설정"));
entries.Add(new IntProperty("outputTagValue_number", "", 0));
entries.Add(new ButtonProperty("AddTagChangeButton", "태그 변경 추가", "").BindClick(SaveOutputTag));
entries.Add(CreateOutputTags(processor));
entries.AddRange(PolicyPropertyBuilder.CreatePolicyEntries(
"ReadyTimePolicy", "준비 시간 정책", "display_mode_ReadyTimePolicy",
() => processor.ready_time_policy, v => processor.ready_time_policy = v,
"ready_time_policy", processor, SaveChange));
entries.AddRange(PolicyPropertyBuilder.CreatePolicyEntries(
"DefectRatePolicy", "불량률 정책", "display_mode_DefectTimePolicy",
() => processor.defect_rate_policy, v => processor.defect_rate_policy = v,
"defect_rate_policy", processor, SaveChange));
entries.Add(CreateRequireResourceGroup(processor));
entries.Add(new BoolProperty("fms_input_enabled", "입력에 FMS 사용", processor.fms_input_enabled) { IsReadOnly = true });
entries.Add(new BoolProperty("fms_output_enabled", "출력에 FMS 사용", processor.fms_output_enabled) { IsReadOnly = true });
propertyWindow.LoadMixedProperties(entries);
PolicyPropertyBuilder.HandleDisplayModeChanged(propertyWindow, "ProcessingTimePolicy", processor.processing_time_policy.policy.ToString());
PolicyPropertyBuilder.HandleDisplayModeChanged(propertyWindow, "ReadyTimePolicy", processor.ready_time_policy.policy.ToString());
PolicyPropertyBuilder.HandleDisplayModeChanged(propertyWindow, "DefectRatePolicy", processor.defect_rate_policy.policy.ToString());
SyncInputPrefabDetailVisibility(processor);
SyncOutputPrefabDetailVisibility(processor);
SyncOutputTagType(ComponentsManager.webconfigData.tags[0].type);
}
private void OnPropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
PolicyPropertyBuilder.TryHandleDisplayModeChanged(e, propertyWindow, "display_mode_ProcessingTimePolicy", "ProcessingTimePolicy");
PolicyPropertyBuilder.TryHandleDisplayModeChanged(e, propertyWindow, "display_mode_ReadyTimePolicy", "ReadyTimePolicy");
PolicyPropertyBuilder.TryHandleDisplayModeChanged(e, propertyWindow, "display_mode_DefectTimePolicy", "DefectRatePolicy");
if (e.PropertyId == "input_selection_policy")
{
SyncInputPrefabDetailVisibility(processor);
}
if (e.PropertyId == "output_selection_policy")
{
SyncOutputPrefabDetailVisibility(processor);
}
}
private PropertyGroup CreatePositionGroup(ProcessorDataClass processor)
{
return PropertyHelper.CreatePositionGroup("processor_position", processor, propertyWindow, SaveChange);
}
#region
public ListProperty BuildOutputTagChanges()
{
List<string> tagKeyList = new List<string>();
foreach (var tag in ComponentsManager.webconfigData.tags)
{
tagKeyList.Add(tag.key);
}
var property = new ListProperty("output_tag_keys", "처리 완료 태그 설정", tagKeyList, tagKeyList[0]);
property.Bind(setter: v => { SyncOutputTagType(ComponentsManager.webconfigData.tags.Find(tag => tag.key == v).type); });
return property;
}
public PropertyGroup CreateOutputTags(ProcessorDataClass processor)
{
var group = new PropertyGroup("output_tags", "", isExpanded: true);
foreach (var tag in processor.output_tag_changes)
{
var property = new ChangeTagProperty($"tag{tag.key}", tag.key, tag.operation, tag.value.ToString());
property.BindClick(() => { processor.output_tag_changes.Remove(tag); SaveChange(processor, processor.output_tag_changes, "output_tag_changes"); InitProcessorProperty(processor); });
property.Bind(setter: v => { tag.value = v; SaveChange(tag, v, "value"); });
group.AddItem(property);
}
return group;
}
public void SyncOutputTagType(string tagType)
{
propertyWindow.SetPropertyVisibility("outputTagType_string", false);
propertyWindow.SetPropertyVisibility("outputTagValue_string", false);
propertyWindow.SetPropertyVisibility("outputTagType_number", false);
propertyWindow.SetPropertyVisibility("outputTagValue_number", false);
propertyWindow.SetPropertyVisibility("AddTagChangeButton", false);
switch (tagType)
{
case "string":
propertyWindow.SetPropertyVisibility("outputTagType_string", true);
propertyWindow.SetPropertyVisibility("outputTagValue_string", true);
propertyWindow.SetPropertyVisibility("outputTagType_number", false);
propertyWindow.SetPropertyVisibility("outputTagValue_number", false);
propertyWindow.SetPropertyVisibility("AddTagChangeButton", true);
break;
case "number":
propertyWindow.SetPropertyVisibility("outputTagType_string", false);
propertyWindow.SetPropertyVisibility("outputTagValue_string", false);
propertyWindow.SetPropertyVisibility("outputTagType_number", true);
propertyWindow.SetPropertyVisibility("outputTagValue_number", true);
propertyWindow.SetPropertyVisibility("AddTagChangeButton", true);
break;
}
}
public void SaveOutputTag()
{
string key = propertyWindow.GetProperty("output_tag_keys").GetValue() as string;
string operation = "";
object value = null;
if (processor.output_tag_changes.Exists(x => x != null && x.key == key))
{
Debug.LogWarning($"이미 '{key}' 태그가 존재합니다.");
return;
}
var tagtype = ComponentsManager.webconfigData.tags?.Find(t => t.key == key);
switch (tagtype.type)
{
case "string":
operation = propertyWindow.GetProperty("outputTagType_string").GetValue() as string;
value = propertyWindow.GetProperty("outputTagValue_string").GetValue();
break;
case "number":
operation = propertyWindow.GetProperty("outputTagValue_number").GetValue() as string;
value = propertyWindow.GetProperty("outputTagValue_number").GetValue();
break;
}
processor.output_tag_changes.Add(new output_tag_changes_base
{
key = key,
operation = operation,
value = value,
});
SaveChange(processor, processor.output_tag_changes, "output_tag_changes");
InitProcessorProperty(processor);
}
#endregion
#region
private PropertyGroup CreateInputPrefabToggleGroup(ProcessorDataClass processor)
{
var group = new PropertyGroup("InputPrefab", "투입 개체", isExpanded: true);
foreach (var prefab in PrefabManager.Instance.prefabDict.Keys)
{
bool enabled = processor.inputs != null && processor.inputs.Exists(x => x.prefab == prefab);
string itemId = $"input_prefab.toggle.{prefab}";
string detailGroupId = GetInputPrefabDetailGroupId(prefab);
var toggle = new BoolProperty($"input_{itemId}", prefab, enabled)
.Bind(setter: v =>
{
if (v) EnsureInputPrefab(processor, prefab);
else RemoveInputPrefab(processor, prefab);
SaveChange(processor, processor.inputs, "inputs");
InitProcessorProperty(processor);
});
group.AddItem(toggle);
}
return group;
}
private static string GetInputPrefabDetailGroupId(string prefabKey)
=> $"InputPrefabDetail.{prefabKey}";
private PropertyGroup CreateInputPrefabDetailGroup(ProcessorDataClass processor, string prefabKey)
{
string groupId = GetInputPrefabDetailGroupId(prefabKey);
var group = new PropertyGroup(groupId, $"{prefabKey} 연결", isExpanded: true);
var entry = GetInputEntry(processor, prefabKey);
if (entry == null)
{
group.AddItem(new StringProperty($"{groupId}.empty", "연결", "투입 개체가 선택되지 않았습니다.")
{
IsReadOnly = true
});
return group;
}
entry.locations ??= new List<Prefab_location>();
string totalId = $"{groupId}.total_count";
var totalProp = new IntProperty(
totalId,
"총 개수",
entry.total_count
)
.Bind(setter: v =>
{
entry.total_count = v;
SaveChange(processor, entry.total_count, $"inputs[{prefabKey}].total_count");
});
group.AddItem(totalProp);
if (entry.locations.Count == 0)
{
group.AddItem(new StringProperty($"{groupId}.no_locs", "연결", "연결된 항목이 없습니다.")
{
IsReadOnly = true
});
return group;
}
foreach (var loc in entry.locations)
{
string locId = $"{groupId}.count.{loc.target}";
var countProp = new IntProperty(
locId,
loc.target,
Mathf.Max(0, loc.count),
isSlider: false,
minValue: 0,
maxValue: 9999
)
.Bind(setter: v =>
{
loc.count = Mathf.Max(0, v);
SaveChange(processor, loc.count, $"inputs[{prefabKey}].locations[target={loc.target}].count");
});
group.AddItem(countProp);
}
return group;
}
private void EnsureInputPrefab(ProcessorDataClass processor, string prefabKey)
{
processor.inputs ??= new List<Prefab_Data>();
if (processor.inputs.Exists(x => x.prefab == prefabKey))
return;
var locations = new List<Prefab_location>();
var workbench = ComponentsManager.webconfigData.property.Processor.workbench.Find(x => x.name == processor.name);
var input = workbench.input_items.Find(x => x.prefab == prefabKey);
if (input != null)
{
foreach (var location in input.locationCounts)
{
var prefablocation = new Prefab_location();
prefablocation.type = location.type;
prefablocation.count = location.count;
prefablocation.target = location.location;
locations.Add(prefablocation);
}
}
processor.inputs.Add(new Prefab_Data
{
prefab = prefabKey,
locations = locations,
total_count = 0
});
}
private void RemoveInputPrefab(ProcessorDataClass processor, string prefabKey)
{
if (processor.inputs == null) return;
int idx = processor.inputs.FindIndex(x => x.prefab == prefabKey);
if (idx >= 0) processor.inputs.RemoveAt(idx);
}
private Prefab_Data GetInputEntry(ProcessorDataClass processor, string prefabKey)
{
if (processor.inputs == null) return null;
return processor.inputs.Find(x => x.prefab == prefabKey);
}
private void SyncInputPrefabDetailVisibility(ProcessorDataClass processor)
{
bool isRandom = string.Equals(processor.input_selection_policy, "random", StringComparison.OrdinalIgnoreCase);
foreach (var prefabKey in PrefabManager.Instance.prefabDict.Keys)
{
bool enabled = processor.inputs != null && processor.inputs.Exists(x => x != null && x.prefab == prefabKey);
string groupId = GetInputPrefabDetailGroupId(prefabKey);
propertyWindow.SetGroupVisibility(groupId, enabled);
if (!enabled)
continue;
var entry = GetInputEntry(processor, prefabKey);
string totalId = $"{groupId}.total_count";
if (propertyWindow.GetProperty(totalId) != null)
propertyWindow.SetPropertyVisibility(totalId, isRandom);
if (entry?.locations != null)
{
foreach (var loc in entry.locations)
{
string locId = $"{groupId}.count.{loc.target}";
if (propertyWindow.GetProperty(locId) != null)
propertyWindow.SetPropertyVisibility(locId, !isRandom);
}
}
}
}
#endregion
#region
private PropertyGroup CreateOutputPrefabToggleGroup(ProcessorDataClass processor)
{
var group = new PropertyGroup("OutputPrefab", "출고 개체", isExpanded: true);
foreach (var prefab in PrefabManager.Instance.prefabDict.Keys)
{
bool enabled = processor.outputs != null && processor.outputs.Exists(x => x.prefab == prefab);
string itemId = $"output_prefab.toggle.{prefab}";
string detailGroupId = GetOutputPrefabDetailGroupId(prefab);
var toggle = new BoolProperty($"output_{itemId}", prefab, enabled)
.Bind(setter: v =>
{
if (v) EnsureOutputPrefab(processor, prefab);
else RemoveOutputPrefab(processor, prefab);
SaveChange(processor, processor.outputs, "outputs");
InitProcessorProperty(processor);
});
group.AddItem(toggle);
}
return group;
}
private static string GetOutputPrefabDetailGroupId(string prefabKey)
=> $"OutputPrefabDetail.{prefabKey}";
private PropertyGroup CreateOutputPrefabDetailGroup(ProcessorDataClass processor, string prefabKey)
{
string groupId = GetOutputPrefabDetailGroupId(prefabKey);
var group = new PropertyGroup(groupId, $"{prefabKey} 연결", isExpanded: true);
var entry = GetOutputEntry(processor, prefabKey);
if (entry == null)
{
group.AddItem(new StringProperty($"{groupId}.empty", "연결", "투입 개체가 선택되지 않았습니다.")
{
IsReadOnly = true
});
return group;
}
entry.locations ??= new List<Prefab_location>();
string totalId = $"{groupId}.total_count";
var totalProp = new IntProperty(
totalId,
"총 개수",
Mathf.Max(0, entry.total_count),
isSlider: false,
minValue: 0,
maxValue: 9999
)
.Bind(setter: v =>
{
entry.total_count = Mathf.Max(0, v);
SaveChange(processor, entry.total_count, $"outputs[{prefabKey}].total_count");
});
group.AddItem(totalProp);
if (entry.locations.Count == 0)
{
group.AddItem(new StringProperty($"{groupId}.no_locs", "연결", "연결된 항목이 없습니다.")
{
IsReadOnly = true
});
return group;
}
foreach (var loc in entry.locations)
{
string locId = $"{groupId}.count.{loc.target}";
var countProp = new IntProperty(
locId,
loc.target,
loc.count
)
.Bind(setter: v =>
{
loc.count = v;
SaveChange(processor, loc.count, $"outputs[{prefabKey}].locations[target={loc.target}].count");
});
group.AddItem(countProp);
}
return group;
}
private void EnsureOutputPrefab(ProcessorDataClass processor, string prefabKey)
{
processor.outputs ??= new List<Prefab_Data>();
if (processor.outputs.Exists(x => x.prefab == prefabKey))
return;
var locations = new List<Prefab_location>();
var workbench = ComponentsManager.webconfigData.property.Processor.workbench.Find(x => x.name == processor.name);
var output = workbench.output_items.Find(x => x.prefab == prefabKey);
if (output != null)
{
foreach (var location in output.locationCounts)
{
var prefablocation = new Prefab_location();
prefablocation.type = location.type;
prefablocation.count = location.count;
prefablocation.target = location.location;
locations.Add(prefablocation);
}
}
processor.outputs.Add(new Prefab_Data
{
prefab = prefabKey,
locations = locations,
total_count = 0
});
}
private void RemoveOutputPrefab(ProcessorDataClass processor, string prefabKey)
{
if (processor.outputs == null) return;
int idx = processor.outputs.FindIndex(x => x.prefab == prefabKey);
if (idx >= 0) processor.outputs.RemoveAt(idx);
}
private Prefab_Data GetOutputEntry(ProcessorDataClass processor, string prefabKey)
{
if (processor.outputs == null) return null;
return processor.outputs.Find(x => x.prefab == prefabKey);
}
private void SyncOutputPrefabDetailVisibility(ProcessorDataClass processor)
{
bool isRandom = string.Equals(processor.output_selection_policy, "random", StringComparison.OrdinalIgnoreCase);
foreach (var prefabKey in PrefabManager.Instance.prefabDict.Keys)
{
bool enabled = processor.outputs != null && processor.outputs.Exists(x => x != null && x.prefab == prefabKey);
string groupId = GetOutputPrefabDetailGroupId(prefabKey);
propertyWindow.SetGroupVisibility(groupId, enabled);
if (!enabled)
continue;
var entry = GetOutputEntry(processor, prefabKey);
string totalId = $"{groupId}.total_count";
if (propertyWindow.GetProperty(totalId) != null)
propertyWindow.SetPropertyVisibility(totalId, isRandom);
if (entry?.locations != null)
{
foreach (var loc in entry.locations)
{
string locId = $"{groupId}.count.{loc.target}";
if (propertyWindow.GetProperty(locId) != null)
propertyWindow.SetPropertyVisibility(locId, !isRandom);
}
}
}
}
#endregion
private PropertyGroup CreateRequireResourceGroup(ProcessorDataClass processor)
{
var group = new PropertyGroup("RequireResource", "자원 할당", isExpanded: true);
var requiredIndex = BuildRequiredIndex(processor);
int count = 0;
foreach (var resource in ComponentsManager.logicDetailData.infrastructure.resources)
{
if (!string.Equals(resource.type, "worker"))
continue;
var key = resource.name;
bool isRequired = key != null && requiredIndex.ContainsKey(key);
var property = new BoolProperty($"required_resource[{count++}]", resource.label, isRequired);
property.Bind(setter: v =>
{
SetRequiredResource(processor, key, v);
});
group.AddItem(property);
}
return group;
}
private void SetRequiredResource(ProcessorDataClass processor, string resourceName, bool required)
{
if (string.IsNullOrEmpty(resourceName)) return;
processor.required_resources ??= new List<Required_Resources>();
int idx = processor.required_resources.FindIndex(x => x != null && x.name == resourceName);
if (required)
{
if (idx < 0)
{
processor.required_resources.Add(new Required_Resources
{
name = resourceName,
count = 1,
selection_policy = "random"
});
}
}
else
{
if (idx >= 0)
processor.required_resources.RemoveAt(idx);
}
}
private Dictionary<string, Required_Resources> BuildRequiredIndex(ProcessorDataClass processor)
{
var dict = new Dictionary<string, Required_Resources>(StringComparer.Ordinal);
if (processor.required_resources == null) return dict;
foreach (var rr in processor.required_resources)
{
if (rr?.name == null) continue;
dict[rr.name] = rr;
}
return dict;
}
private void OnDestroy()
{
if (propertyWindow != null)
propertyWindow.PropertyValueChanged -= OnPropertyValueChanged;
}
}