This commit is contained in:
2025-11-04 11:02:02 +09:00
parent 984e9188c1
commit bb7ed2d6dc
333 changed files with 50301 additions and 8202 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d41ad1523c22d2b4cb6d5a7963823faf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,160 @@
using Gpm.Ui.Sample;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SocialPlatforms;
using UVC.Data.Core;
namespace Simulator.Data.Transport
{
public class AGV : ComponentBase
{
private Vector3 targetPosition;
private Vector3 startPosition;
private Quaternion targetRotation;
private float moveSpeed;
private float t = 0f;
public GameObject entitySocket;
public List<Entity> possessEntities = new List<Entity>();
int index = 0;
public AGVData data;
bool getdata = false;
public void UpdatePosition(Vector3 position)
{
transform.position = position;
}
public void SetTargetPosition(DataObject agvData)
{
var datas = agvData.GetDataObject("data");
var timing = datas.GetDataObject("timing");
var duration = timing.GetDataObject("duration");
targetPosition = AGVNodeManager.Instance.GetNodePosition(datas.GetString("to_node"));
startPosition = AGVNodeManager.Instance.GetNodePosition(datas.GetString("from_node"));
moveSpeed = (float)duration.GetFloat("real_seconds");
t = 0f;
getdata = true;
//Debug.Log($"{datas.GetDateTime("timestamp").ToString()}{index}{data.name},from:{datas.GetString("from_node")},to:{datas.GetString("to_node")}");
//Debug.Log($"move{agvData.GetString("component_id")}");
}
private void PlaceNext(Entity entity)
{
if (entity == null) return;
if (entitySocket == null)
{
Debug.LogWarning("[AGV] entitySocket 이 null 입니다. 배치를 건너뜁니다.");
return;
}
// 부모/로컬 트랜스폼 설정
entity.transform.SetParent(entitySocket.transform, worldPositionStays: false);
// Z 방향 적층(기존 로직 유지)
float stepZ = EntityManager.Instance.ObjectSize.z;
entity.transform.localPosition = new Vector3(0f, 0f, stepZ * index);
Debug.Log(stepZ);
Debug.Log(index);
entity.transform.localRotation = Quaternion.identity;
// 중복 방지 후 등록
if (!possessEntities.Contains(entity))
possessEntities.Add(entity);
index++;
}
public void LoadEntity(DataObject agvData)
{
var datas = agvData.GetDataObject("data");
var entityIds = datas["entity_ids"].ConvertTo<List<string>>();
if (entityIds == null || entityIds.Count == 0) return;
var entities = EntityManager.Instance.GetEntities(entityIds, this);
if (entities == null || entities.Count == 0) return;
foreach (var e in entities)
{
PlaceNext(e);
}
}
public override void DecreaseEntity(Entity entity)
{
if (entity == null) return;
// 내부 리스트에서 제거(부모 변경/파괴 등 어떤 상태든 Remove는 안전)
possessEntities.Remove(entity);
// 재배치
ReflowEntities();
}
/// <summary>
/// 현재 보유 엔티티들을 0번 인덱스부터 다시 적층하여 빈칸 제거
/// 순회-수정 예외 방지를 위해 스냅샷을 만든 뒤, 원본을 비우고 다시 채움
/// </summary>
public void ReflowEntities()
{
// 위치 인덱스 초기화
index = 0;
// 스냅샷 생성 (null 제거)
var snapshot = new List<Entity>(possessEntities.Count);
foreach (var e in possessEntities)
{
if (e != null) snapshot.Add(e);
}
// 원본을 비운 뒤 스냅샷 기반으로 재배치
possessEntities.Clear();
foreach (var e in snapshot)
{
PlaceNext(e);
}
}
void Update()
{
if (getdata)
{
if (moveSpeed <= 0f) return;
t += Time.deltaTime / moveSpeed;
// 위치 업데이트
if (t <= 1f)
{
transform.position = Vector3.Lerp(startPosition, targetPosition, t);
}
else
{
transform.position = targetPosition;
getdata = false;
}
}
/*
// 회전 업데이트
if (isRotating)
{
float currentAngle = transform.eulerAngles.y;
float targetAngle = targetRotation.eulerAngles.y;
float newAngle = Mathf.SmoothDampAngle(currentAngle, targetAngle, ref angularVelocity, rotationSpeed, Mathf.Infinity, dampedTime);
transform.rotation = Quaternion.Euler(0, newAngle, 0);
}
else
{
transform.rotation = targetRotation;
angularVelocity = 0f;
}
*/
}
}
}

View File

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

View File

@@ -0,0 +1,22 @@
using System;
namespace Simulator.Data.Transport
{
[Serializable]
public class AGVData
{
public string name;
public string label;
public string initial_node;
public int max_load_capacity;
public AGVTimePolicy loading_time_policy;
public AGVTimePolicy unloading_time_policy;
}
[Serializable]
public class AGVTimePolicy
{
public string type;
public int value;
}
}

View File

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

View File

@@ -0,0 +1,163 @@
using Cysharp.Threading.Tasks;
using NUnit.Framework.Interfaces;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SocialPlatforms;
using UVC.Core;
using UVC.Data;
using UVC.Data.Core;
using UVC.Data.Mqtt;
using UVC.Factory.Component;
using UVC.Pool;
namespace Simulator.Data.Transport
{
public enum AGVDataType
{
Moving,
Loading,
UnLoading
}
public class AGVManager : SingletonScene<AGVManager>
{
private readonly Dictionary<string, string> prefabPaths = new Dictionary<string, string>()
{
{"agv","prefabs/agv"},
};
private GameObjectPool<AGV>? agvPool;
public GameObjectPool<AGV> AGVPool
{
get
{
if (agvPool == null)
{
//Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return agvPool!;
}
}
private DataMapper agvMoveMapper;
private DataMapper agvLoadMapper;
private DataMapper agvUnLoadMapper;
protected override void Init()
{
InitializeAGVPoolAsync().ContinueWith(() =>
{
var agvMoveDataMask = new DataMask();
agvMoveDataMask.ObjectName = "agv"; // AGV 객체의 이름을 설정합니다.
agvMoveDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
agvMoveDataMask["component_id"] = "";
agvMoveDataMask["event_name"] = "";
agvMoveDataMask["timestamp"] = new DateTime();
agvMoveDataMask["data"] = new DataMask()
{
["from_node"] = "",
["to_node"] = "",
["timing"] = new DataMask()
{
["duration"] = new DataMask()
{
["real_seconds"] = 0.0f
}
}
};
agvMoveMapper = new DataMapper(agvMoveDataMask);
var agvLoadDataMask = new DataMask();
agvLoadDataMask.ObjectName = "agv"; // AGV 객체의 이름을 설정합니다.
agvLoadDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
agvLoadDataMask["component_id"] = "";
agvLoadDataMask["event_name"] = "";
agvLoadDataMask["timestamp"] = new DateTime();
agvLoadDataMask["data"] = new DataMask()
{
["from_node"] = "",
["to_node"] = "",
["entity_ids"] = new List<string>(),
};
agvLoadMapper = new DataMapper(agvLoadDataMask);
var agvUnLoadDataMask = new DataMask();
agvUnLoadDataMask.ObjectName = "agv"; // AGV 객체의 이름을 설정합니다.
agvUnLoadDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
agvUnLoadDataMask["component_id"] = "";
agvUnLoadDataMask["event_name"] = "";
agvUnLoadDataMask["timestamp"] = new DateTime();
agvUnLoadDataMask["data"] = new DataMask()
{
["component"]="",
["entity_ids"] = new List<string>(),
};
agvUnLoadMapper = new DataMapper(agvUnLoadDataMask);
});
}
public void InitAGV(List<AGVData> datas)
{
foreach (var data in datas)
{
var agv = agvPool.GetItem($"{data.name}");
agv.data = data;
agv.UpdatePosition(AGVNodeManager.Instance.GetNodePosition(data.initial_node));
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/moving");
var mqttConfigAGVMoving = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/moving");
mqttConfigAGVMoving.SetDataMapper(agvMoveMapper);
mqttConfigAGVMoving.SetHandler((value) => OnUpdateData(value, AGVDataType.Moving));
DataRepository.Instance.MqttReceiver.Add(mqttConfigAGVMoving);
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/loading_end");
var mqttConfigAGVLoading = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/loading_end");
mqttConfigAGVLoading.SetDataMapper(agvLoadMapper);
mqttConfigAGVLoading.SetHandler((value) => OnUpdateData(value, AGVDataType.Loading));
DataRepository.Instance.MqttReceiver.Add(mqttConfigAGVLoading);
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/unloading_end");
var mqttConfigAGVUnLoading = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/unloading_end");
mqttConfigAGVUnLoading.SetDataMapper(agvUnLoadMapper);
mqttConfigAGVUnLoading.SetHandler((value) => OnUpdateData(value, AGVDataType.UnLoading));
DataRepository.Instance.MqttReceiver.Add(mqttConfigAGVUnLoading);
}
}
public void OnUpdateData(IDataObject data, AGVDataType type)
{
if (data == null) return;
DataObject? obj = data as DataObject;
if (obj == null) return;
//Debug.Log($"OnUpdateData:{obj}, {obj["data"]}");
AGV? agv = agvPool.GetItem(obj.GetString("component_id")!);
switch (type)
{
case AGVDataType.Moving:
agv.SetTargetPosition(obj);
break;
case AGVDataType.Loading:
agv.LoadEntity(obj);
break;
case AGVDataType.UnLoading:
//Debug.Log("unload");
//agv.UnLoadEntity(obj);
break;
}
}
private async UniTask InitializeAGVPoolAsync()
{
if (agvPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["agv"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["agv"]}");
return;
}
agvPool = new GameObjectPool<AGV>(prefab, transform);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 40a549d01589ef14bb8b3d3c57a05619

View File

@@ -0,0 +1,10 @@
using Simulator.Data.Transport;
using UnityEngine;
namespace Simulator.Data.Transport
{
public class AGVNode : MonoBehaviour
{
public NodeDataClass data;
}
}

View File

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

View File

@@ -0,0 +1,109 @@
using Cysharp.Threading.Tasks;
using System.Collections.Generic;
using UnityEngine;
using UVC.Core;
using UVC.Pool;
namespace Simulator.Data.Transport
{
public class AGVNodeManager : SingletonScene<AGVNodeManager>
{
private readonly Dictionary<string, string> prefabPaths = new Dictionary<string, string>()
{
{"node","prefabs/Node"},
{"path","prefabs/Path"},
};
Dictionary<string, AGVNode> nodeDict = new Dictionary<string, AGVNode>();
private GameObjectPool<AGVNode>? nodePool;
private GameObjectPool<AGVPath>? pathPool;
public GameObjectPool<AGVNode> NodePool
{
get
{
if (nodePool == null)
{
//Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return nodePool!;
}
}
public GameObjectPool<AGVPath> PathPool
{
get
{
if (pathPool == null)
{
//Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return pathPool!;
}
}
protected override void Init()
{
InitializeNodePoolAsync().ContinueWith(() =>
{
});
InitializePathPoolAsync().ContinueWith(() =>
{
});
}
public Vector3 GetNodePosition(string name)
{
if (!nodeDict.ContainsKey(name))
{
return Vector3.zero;
}
return nodeDict[name].transform.position;
}
public void SpawnNode(List<NodeDataClass> datas)
{
foreach (var data in datas)
{
var node = nodePool.GetItem($"{data.id}");
node.transform.position = data.GetPosition();
node.data = data;
nodeDict.Add(data.id, node);
}
}
public void LinkNode(List<PathDataClass> datas)
{
foreach (var data in datas)
{
var path = pathPool.GetItem($"{data.from}{data.to}");
path.SetPathData(data, GetNodePosition(data.from), GetNodePosition(data.to));
}
}
private async UniTask InitializeNodePoolAsync()
{
if (nodePool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["node"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["node"]}");
return;
}
nodePool = new GameObjectPool<AGVNode>(prefab, transform);
}
private async UniTask InitializePathPoolAsync()
{
if (pathPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["path"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["path"]}");
return;
}
pathPool = new GameObjectPool<AGVPath>(prefab, transform);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5eabff7fec7b41b4a9d71f4e3e07a802

View File

@@ -0,0 +1,22 @@
using Simulator.Data.Transport;
using System.IO;
using UnityEngine;
using UVC.Data.Core;
public class AGVPath : MonoBehaviour
{
PathDataClass data;
LineRenderer lineRenderer;
private void Awake()
{
lineRenderer=GetComponent<LineRenderer>();
}
public void SetPathData(PathDataClass data,Vector3 from,Vector3 to)
{
this.data = data;
lineRenderer.SetPosition(0, from);
lineRenderer.SetPosition(1, to);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7a2b122b7b81e124bb04810ff54ff57a

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 303872d5c57e4ed449cb0415d729ca4f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System;
using UnityEngine;
namespace Simulator.Data.Transport
{
[Serializable]
public class NodeDataClass
{
public string id="";
public string type="";
public Position position=new Position();
public Properties properties=new Properties();
public Vector3 GetPosition()
{
return new Vector3(position.x,position.z, position.y);
}
}
[Serializable]
public class Properties
{
public int capacity=0;
public string description= "";
}
}

View File

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

View File

@@ -0,0 +1,13 @@
using System;
using UnityEngine;
namespace Simulator.Data.Transport
{
[Serializable]
public class PathDataClass
{
public string to = "";
public string from = "";
public bool bidirectional = true;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6c7b8b8c9de345046beea547c59e9e4b

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 25a0f380ffc52174fa79a438fea26374
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using Gpm.Ui.Sample;
using Simulator.Data;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEditor.Rendering;
using UnityEngine;
using UVC.Data.Core;
public class ASRSComponent : ComponentBase
{
ASRSDataClass asrsData = new ASRSDataClass();
public RackComponent rack;
public StackerCrane stacker;
public void SetComponent(ASRSDataClass asrsData)
{
this.asrsData = asrsData;
data = asrsData;
SetRack(asrsData.asrs_layout);
SetStackerCrane(asrsData.asrs_layout);
}
public override void GetModelData(DataObject modelData)
{
var name = modelData.GetString("event_name");
if (string.Equals(name,"moving"))
{
stacker.SetTargetPosition(modelData,rack);
}
if (string.Equals(name, "fork_operation"))
{
stacker.SetForkPosition(modelData,rack);
}
if (string.Equals(name, "arrived"))
{
rack.GetModelData(modelData);
}
}
public void SetRack(rack_layout layout)
{
rack.SpawnCell(layout,true);
}
void SetStackerCrane(rack_layout layout)
{
stacker.SetPosition(layout);
}
}

View File

@@ -0,0 +1,169 @@
using Simulator.Data;
using Simulator.Data.Transport;
using System;
using TMPro;
using UnityEngine;
using UVC.Data.Core;
public class StackerCrane : ComponentBase
{
[SerializeField]
GameObject horizonObject;
[SerializeField]
GameObject verticalObject;
[SerializeField]
GameObject forkObject;
bool horizontalPosition = false;
bool verticalPosition = false;
bool forkPosition = false;
bool operationflag = false;
bool operationcounting = false;
Vector3 horizontalTarget;
Vector3 verticalTarget;
Vector3 forkTarget;
Vector3 horizontalStart;
Vector3 verticalStart;
Vector3 forkStart;
Vector3 startposition;
string entityId;
float horizontalTime;
float verticalTime;
float forkTime;
float ht;
float vt;
float ft;
public void SetPosition(rack_layout layout)
{
horizonObject.transform.localPosition = new Vector3(layout.x_length * -0.55f, layout.z_length * 0, layout.y_length * -0.5f);
startposition = new Vector3(layout.x_length * -0.55f, layout.z_length * 0, layout.y_length * -0.5f);
forkStart = new Vector3(0, 0, 0.3f);
}
public void SetTargetPosition(DataObject agvData, RackComponent rack)
{
var datas = agvData.GetDataObject("data");
var crane_movement = datas.GetDataObject("crane_movement");
horizontalTime = (float)crane_movement.GetFloat("horizontal_time");
horizontalTime = horizontalTime / 10f;
verticalTime = (float)crane_movement.GetFloat("vertical_time");
verticalTime = verticalTime / 10f;
var from = crane_movement.GetDataObject("from_position");
var fx = (int)from.GetInt("x");
var fy = (int)from.GetInt("y");
var fz = (int)from.GetInt("z");
horizontalStart = new Vector3(horizonObject.transform.position.x, 0, startposition.z);
verticalStart = new Vector3(-0.03920534f, -0.2531985f, verticalObject.transform.position.z);
var to = crane_movement.GetDataObject("to_position");
var tx = (int)to.GetInt("x");
var ty = (int)to.GetInt("y");
var tz = (int)to.GetInt("z");
var rackp = rack.cellComponents[(tx, tz, ty)].transform.position;
//Debug.Log($"{tx},{ty},{tz}");
horizontalTarget = new Vector3(rackp.x, 0, startposition.z);
verticalTarget = new Vector3(-0.03920534f, -0.2531985f, rackp.y);
ht = 0f;
vt = 0f;
horizontalPosition = true;
verticalPosition = true;
}
public void SetForkPosition(DataObject agvData, RackComponent rack)
{
var datas = agvData.GetDataObject("data");
entityId = datas.GetString("entity_id");
var operation = datas.GetString("operation");
if (string.Equals(operation, "up"))
{
operationflag = true;
}
else
{
operationflag = false;
}
operationcounting = true;
var timing = datas.GetDataObject("timing");
forkTime = (float)timing.GetFloat("total");
var position = datas.GetDataObject("position");
var x = (int)position.GetInt("x");
var y = (int)position.GetInt("y");
var z = (int)position.GetInt("z");
var rackp = rack.cellComponents[(x, z, y)].transform.position;
ft = 0f;
forkTarget = new Vector3(-rackp.z+startposition.x,0, 0.3f);
Debug.Log($"t{forkTarget}");
Debug.Log($"s{forkStart}");
forkPosition = true;
}
void Update()
{
if (horizontalPosition)
{
if (horizontalTime >= 0f)
{
ht += Time.deltaTime / horizontalTime;
// 위치 업데이트
if (ht <= 1f)
{
horizonObject.transform.localPosition = Vector3.Lerp(horizontalStart, horizontalTarget, ht);
}
else
{
horizonObject.transform.localPosition = horizontalTarget;
horizontalPosition = false;
}
}
}
if (verticalPosition)
{
if (verticalTime >= 0f)
{
vt += Time.deltaTime / verticalTime;
// 위치 업데이트
if (vt <= 1f)
{
verticalObject.transform.localPosition = Vector3.Lerp(verticalStart, verticalTarget, vt);
}
else
{
verticalObject.transform.localPosition = verticalTarget;
verticalPosition = false;
}
}
}
if (forkPosition)
{
if (forkTime >= 0f)
{
ft += Time.deltaTime / forkTime;
// 위치 업데이트
if (ft <= 0.5f)
{
forkObject.transform.localPosition = Vector3.Lerp(forkStart, forkTarget, ft);
}
else if (ft >= 0.5f && ft <= 1f)
{
if (operationcounting)
{
if (operationflag)
{
var entity = EntityManager.Instance.GetEntity(entityId,this);
entity.transform.parent = forkObject.transform;
entity.transform.localPosition = new Vector3(0, 0, 0);
}
operationcounting = false;
}
forkObject.transform.localPosition = Vector3.Lerp(forkTarget, forkStart, ft);
}
else
{
forkObject.transform.localPosition = forkStart;
forkPosition = false;
}
}
}
}
}

View File

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

View File

@@ -1,12 +0,0 @@
using Simulator.Data;
using UnityEngine;
public class ASRSComponent : ComponentBase
{
ASRSDataClass asrsData = new ASRSDataClass();
public void ChangeComponent(ASRSDataClass asrsData)
{
this.asrsData = asrsData;
data = asrsData;
}
}

View File

@@ -0,0 +1,6 @@
using UnityEngine;
public class CellComponent : MonoBehaviour
{
public GameObject Socket;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8c30230088e7bde47afaf84efbcb905f

View File

@@ -7,6 +7,8 @@ public enum ComponentType
{
Source,
Sink,
Rack,
Queue,
ASRS
}
public class ComponentBase : MonoBehaviour
@@ -23,4 +25,9 @@ public class ComponentBase : MonoBehaviour
public virtual void GetModelData(DataObject modelData)
{
}
public virtual void DecreaseEntity(Entity entity)
{
}
}

View File

@@ -1,247 +1,387 @@
using Cysharp.Threading.Tasks;
using Simulator.Config;
using Simulator.Data;
using Simulator.Data.Transport;
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UVC.Core;
using UVC.Data;
using UVC.Data.Core;
using UVC.Data.Mqtt;
using UVC.Factory.Component;
using UVC.Network;
using UVC.Pool;
using static Unity.VisualScripting.Member;
using static UnityEditor.Progress;
public class ComponentsManager : SingletonScene<ComponentsManager>
namespace Simulator.Data
{
private readonly Dictionary<string, string> prefabPaths = new Dictionary<string, string>()
public class ComponentsManager : SingletonScene<ComponentsManager>
{
private readonly Dictionary<string, string> prefabPaths = new Dictionary<string, string>()
{
{"source","prefabs/pallet"},
{"queue","prefabs/pallet"},
{"queue","prefabs/queue"},
{"rack","prefabs/rack"},
{"sink","prefabs/Sink_Container"},
{"asrs","prefabs/Store_BufferRack"},
{"asrs","prefabs/ASRS"},
};
Dictionary<string, ComponentBase> componentDatas=new Dictionary<string, ComponentBase>();
Dictionary<ComponentType, DataMapper> dataMapperDict=new Dictionary<ComponentType, DataMapper>();
public Dictionary<string, ComponentBase> componentDatas = new Dictionary<string, ComponentBase>();
Dictionary<ComponentType, DataMapper> dataMapperDict = new Dictionary<ComponentType, DataMapper>();
private GameObjectPool<SourceComponent>? sourcePool;
private GameObjectPool<SinkComponent>? sinkPool;
private GameObjectPool<ASRSComponent>? asrsPool;
private GameObjectPool<SourceComponent>? sourcePool;
private GameObjectPool<SinkComponent>? sinkPool;
private GameObjectPool<QueueComponent>? queuePool;
private GameObjectPool<RackComponent>? rackPool;
private GameObjectPool<ASRSComponent>? asrsPool;
public logicDetailData logicDetailData;
public SimulatorCodeDataClass codedata;
public LogicDetailData logicDetailData;
public SimulatorCodeDataClass codedata;
public GameObjectPool<SourceComponent> SourcePool
{
get
public GameObjectPool<SourceComponent> SourcePool
{
if (sourcePool == null)
get
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
if (sourcePool == null)
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return sourcePool!;
}
return sourcePool!;
}
}
public GameObjectPool<SinkComponent> SinkPool
{
get
public GameObjectPool<SinkComponent> SinkPool
{
if (sourcePool == null)
get
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
if (sourcePool == null)
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return sinkPool!;
}
return sinkPool!;
}
}
public GameObjectPool<ASRSComponent> AsrsPool
{
get
public GameObjectPool<QueueComponent> QueuePool
{
if (asrsPool == null)
get
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
if (queuePool == null)
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return queuePool!;
}
return asrsPool!;
}
}
protected override void Init()
{
InitializeSourcePoolAsync().ContinueWith(() =>
public GameObjectPool<RackComponent> RackPool
{
//playback에서도 데이터를 업데이트 하기에 DataRepository에 핸들러를 추가합니다.
// DataRepository.Instance.AddDataUpdateHandler("AGV", OnUpdateData);
var sourceDataMask = new DataMask();
sourceDataMask.ObjectName = "Source"; // AGV 객체의 이름을 설정합니다.
sourceDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
sourceDataMask["component_id"] = "";
sourceDataMask["data"] = new DataMask()
get
{
["total_entity"] = 0
if (rackPool == null)
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return rackPool!;
}
}
public GameObjectPool<ASRSComponent> AsrsPool
{
get
{
if (asrsPool == null)
{
Debug.LogError("Pool is not initialized. Please call InitializePoolAsync first.");
}
return asrsPool!;
}
}
protected override void Init()
{
InitializeSourcePoolAsync().ContinueWith(() =>
{
var sourceDataMask = new DataMask();
sourceDataMask.ObjectName = "Source"; // AGV 객체의 이름을 설정합니다.
sourceDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
sourceDataMask["component_id"] = "";
sourceDataMask["event_name"] = "";
sourceDataMask["timestamp"] = new DateTime();
sourceDataMask["data"] = new DataMask()
{
["entity_ids"] = new List<string>(),
["total_entity"] = 0
};
DataMapper sourceMapper = new DataMapper(sourceDataMask);
dataMapperDict.Add(ComponentType.Source, sourceMapper);
});
InitializeSinkPoolAsync().ContinueWith(() =>
{
var sinkDataMask = new DataMask();
sinkDataMask.ObjectName = "Sink"; // AGV 객체의 이름을 설정합니다.
sinkDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
sinkDataMask["component_id"] = "";
sinkDataMask["event_name"] = "";
sinkDataMask["timestamp"] = new DateTime();
sinkDataMask["data"] = new DataMask()
{
["entity_ids"] = new List<string>(),
};
DataMapper sinkmapper = new DataMapper(sinkDataMask);
dataMapperDict.Add(ComponentType.Sink, sinkmapper);
});
InitializeQueuePoolAsync().ContinueWith(() =>
{
dataMapperDict.Add(ComponentType.Queue, null);
});
InitializeRackPoolAsync().ContinueWith(() =>
{
var rackDataMask = new DataMask();
rackDataMask.ObjectName = "Rack";
rackDataMask.ObjectIdKey = "component_id";
rackDataMask["component_id"] = "";
rackDataMask["event_name"] = "";
rackDataMask["timestamp"] = new DateTime();
rackDataMask["data"] = new DataMask()
{
["entity_id"] = "",
["coordinates"] = new DataMask()
{
["x"] = 0,
["y"] = 0,
["z"] = 0
}
};
DataMapper rackmapper = new DataMapper(rackDataMask);
dataMapperDict.Add(ComponentType.Rack, rackmapper);
});
InitializeAsrsPoolAsync().ContinueWith(() =>
{
var asrsDataMask = new DataMask();
DataMapper asrsmapper = new DataMapper(asrsDataMask);
dataMapperDict.Add(ComponentType.ASRS, asrsmapper);
});
testRequest();
}
private async void testRequest()
{
var data = await HttpRequester.RequestGet<Totaljson>($"{Constants.HTTP_DOMAIN}/simulation/logics/35", null, null, true);
logicDetailData = data.data.data;
SimulatorCreateRequestParameter param = new SimulatorCreateRequestParameter();
param.speed = SimulationConfig.speed;
Dictionary<string, object> body = new Dictionary<string, object>()
{
{ "projectId",18 },
{ "logicId",35 },
{ "logicData",null },
{ "name","새 시뮬레이션2" },
{ "parameters",param }
};
DataMapper mapper = new DataMapper(sourceDataMask);
dataMapperDict.Add(ComponentType.Source, mapper);
/*
var mqttConfig = new MqttSubscriptionConfig("Source");
mqttConfig.SetDataMapper(mapper);
// 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다.
DataRepository.Instance.MqttReceiver.Add(mqttConfig);
*/
});
InitializeSinkPoolAsync().ContinueWith(() =>
{
var sinkDataMask = new DataMask();
sinkDataMask.ObjectName = "Sink"; // AGV 객체의 이름을 설정합니다.
sinkDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
/*
sinkDataMask["component_id"] = "";
sinkDataMask["data"] = new DataMask()
var cdata = await HttpRequester.RequestPost<SimulatorCodeDataClass>($"{Constants.HTTP_DOMAIN}/simulation/histories", body, null, true);
codedata = cdata;
SimulationConfig.SimulationCode = codedata.data.data.simulationCode;
if (data.data.data.production_system.conveyors != null && data.data.data.production_system.conveyors.Count >= 1)
{
//["sink_id"] = 0
};
*/
ConveyorManager.Instance.Build(data.data.data.production_system.conveyors);
}
if (data.data.data.transport_system != null)
{
if (data.data.data.transport_system.node_networks != null && data.data.data.transport_system.node_networks.Count >= 1)
{
AGVNodeManager.Instance.SpawnNode(data.data.data.transport_system.node_networks[0].nodes);
AGVNodeManager.Instance.LinkNode(data.data.data.transport_system.node_networks[0].paths);
}
if (data.data.data.transport_system.transport_managers != null && data.data.data.transport_system.transport_managers.Count >= 1)
{
if (data.data.data.transport_system.transport_managers[0].vehicle_fleets != null && data.data.data.transport_system.transport_managers[0].vehicle_fleets.Count >= 1)
{
AGVManager.Instance.InitAGV(data.data.data.transport_system.transport_managers[0].vehicle_fleets[0].vehicles);
}
}
}
SpawnComponents(data.data.data);
SubscribeTopic();
DataRepository.Instance.MqttReceiver.Start();
}
DataMapper mapper = new DataMapper(sinkDataMask);
dataMapperDict.Add(ComponentType.Sink, mapper);
});
InitializeAsrsPoolAsync().ContinueWith(() =>
void SubscribeTopic()
{
});
testRequest();
}
foreach (var componentdata in componentDatas)
{
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{codedata.data.data.simulationCode}/components/+/{componentdata.Key}/#");
var mqttConfig = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{componentdata.Key}/#");
mqttConfig.SetDataMapper(dataMapperDict[componentdata.Value.componentType]);
mqttConfig.SetHandler((value) => OnUpdateData(value, componentdata.Value.componentType));
//mqttConfig.SetHandler((value)=>Debug.Log(value));
DataRepository.Instance.MqttReceiver.Add(mqttConfig);
}
}
private async void testRequest()
{
var data = await HttpRequester.RequestGet<totaljson>($"{Constants.HTTP_DOMAIN}/simulation/logics/30", null, null, true);
SimulatorCreateRequestParameter param=new SimulatorCreateRequestParameter();
Dictionary<string, object> body = new Dictionary<string, object>()
public void OnUpdateData(IDataObject data, ComponentType type)
{
{ "projectId",18 },
{ "logicId",30 },
{ "logicData",null },
{ "name","새 시뮬레이션2" },
{ "parameters",param }
};
var cdata = await HttpRequester.RequestPost<SimulatorCodeDataClass>($"{Constants.HTTP_DOMAIN}/simulation/histories", body, null, true);
codedata = cdata;
logicDetailData = data.data.data;
SpawnComponents(data.data.data);
SubscribeTopic();
DataRepository.Instance.MqttReceiver.Start();
}
void SubscribeTopic()
{
foreach (var componentdata in componentDatas)
if (data == null) return;
DataObject? obj = data as DataObject;
if (obj == null) return;
//Debug.Log($"OnUpdateData:{obj}, {obj["data"]}");
switch (type)
{
case ComponentType.Source:
if (string.Equals(obj.GetString("event_name"), "completed"))
{
SourceComponent? source = sourcePool.GetItem(obj.GetString("component_id")!);
source.GetModelData(obj);
}
break;
case ComponentType.Sink:
if (string.Equals(obj.GetString("event_name"), "completed"))
{
SinkComponent? sink = sinkPool.GetItem(obj.GetString("component_id")!);
sink.GetModelData(obj);
Debug.Log(obj);
}
break;
case ComponentType.Queue:
if (string.Equals(obj.GetString("event_name"), "completed"))
{
QueueComponent? queue = QueuePool.GetItem(obj.GetString("component_id")!);
queue.GetModelData(obj);
}
break;
case ComponentType.Rack:
if (string.Equals(obj.GetString("event_name"), "arrived"))
{
RackComponent? rack = rackPool.GetItem(obj.GetString("component_id")!);
rack.GetModelData(obj);
}
break;
case ComponentType.ASRS:
ASRSComponent? asrs = asrsPool.GetItem(obj.GetString("component_id")!);
asrs.GetModelData(obj);
break;
}
}
private async UniTask InitializeSourcePoolAsync()
{
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{codedata.data.data.simulationCode}/components/+/{componentdata.Key}/#");
var mqttConfig = new MqttSubscriptionConfig($"simulation/{codedata.data.data.simulationCode}/components/+/{componentdata.Key}/#");
mqttConfig.SetDataMapper(dataMapperDict[componentdata.Value.componentType]);
mqttConfig.SetHandler((value)=>OnUpdateData(value, componentdata.Value.componentType));
DataRepository.Instance.MqttReceiver.Add(mqttConfig);
if (sourcePool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["source"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["source"]}");
return;
}
sourcePool = new GameObjectPool<SourceComponent>(prefab, transform);
}
private async UniTask InitializeSinkPoolAsync()
{
if (sinkPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["sink"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["sink"]}");
return;
}
sinkPool = new GameObjectPool<SinkComponent>(prefab, transform);
}
private async UniTask InitializeQueuePoolAsync()
{
if (queuePool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["queue"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["queue"]}");
return;
}
queuePool = new GameObjectPool<QueueComponent>(prefab, transform);
}
private async UniTask InitializeRackPoolAsync()
{
if (rackPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["rack"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["rack"]}");
return;
}
rackPool = new GameObjectPool<RackComponent>(prefab, transform);
}
private async UniTask InitializeAsrsPoolAsync()
{
if (asrsPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["asrs"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["asrs"]}");
return;
}
asrsPool = new GameObjectPool<ASRSComponent>(prefab, transform);
}
private void SpawnComponents(LogicDetailData data)
{
foreach (var component in data.production_system.sources)
{
var source = sourcePool.GetItem($"{component.name}");
source.componentType = ComponentType.Source;
source.SetComponent(component);
source.SetPosition();
componentDatas.Add(component.name, source);
}
foreach (var component in data.production_system.sinks)
{
var sink = sinkPool.GetItem($"{component.name}");
sink.componentType = ComponentType.Sink;
sink.SetComponent(component);
sink.SetPosition();
componentDatas.Add(component.name, sink);
}
foreach (var component in data.infrastructure.queues)
{
var queue = queuePool.GetItem($"{component.name}");
queue.componentType = ComponentType.Queue;
queue.SetComponent(component);
queue.SetPosition();
componentDatas.Add(component.name, queue);
}
if (data.production_system.racks != null)
{
foreach (var component in data.production_system.racks)
{
var rack = rackPool.GetItem($"{component.name}");
rack.componentType = ComponentType.Rack;
rack.SetComponent(component);
componentDatas.Add(component.name, rack);
}
}
if (data.production_system.asrs != null)
{
foreach (var component in data.production_system.asrs)
{
var asrs = asrsPool.GetItem($"{component.name}");
asrs.componentType = ComponentType.ASRS;
asrs.SetComponent(component);
componentDatas.Add(component.name, asrs);
}
}
}
}
public void OnUpdateData(IDataObject data,ComponentType type)
{
if (data == null) return;
DataObject? obj = data as DataObject;
if (obj == null) return;
Debug.Log($"OnUpdateData:{obj}, {obj["data"]}");
switch (type)
{
case ComponentType.Source:
SourceComponent? source = sourcePool.GetItem(obj.GetString("component_id")!);
source.GetModelData(obj);
break;
case ComponentType.Sink:
break;
case ComponentType.ASRS:
break;
}
}
private async UniTask InitializeSourcePoolAsync()
{
if (sourcePool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["source"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["source"]}");
return;
}
sourcePool = new GameObjectPool<SourceComponent>(prefab, transform);
}
private async UniTask InitializeSinkPoolAsync()
{
if (sinkPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["sink"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["sink"]}");
return;
}
sinkPool = new GameObjectPool<SinkComponent>(prefab, transform);
}
private async UniTask InitializeAsrsPoolAsync()
{
if (asrsPool != null) return;
var prefab = await Resources.LoadAsync<GameObject>(prefabPaths["asrs"]) as GameObject;
if (prefab == null)
{
Debug.LogError($"Prefab not found at path: {prefabPaths["asrs"]}");
return;
}
asrsPool = new GameObjectPool<ASRSComponent>(prefab, transform);
}
private void SpawnComponents(logicDetailData data)
{
foreach (var component in data.production_system.sources)
{
var source=sourcePool.GetItem($"{component.name}");
source.componentType = ComponentType.Source;
source.SetComponent(component);
source.SetPosition();
componentDatas.Add(component.name, source);
}
foreach (var component in data.production_system.sinks)
{
var sink = sinkPool.GetItem($"{component.name}");
sink.componentType = ComponentType.Sink;
sink.ChangeComponent(component);
sink.SetPosition();
componentDatas.Add(component.name, sink);
}
/*
foreach (var component in data.production_system.asrs)
{
var asrs = asrsPool.GetItem($"{component.name}");
asrs.componentType = ComponentType.ASRS;
asrs.ChangeComponent(component);
//asrs.SetPosition();
componentDatas.Add(component.name, asrs);
}
*/
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 12d78a9e5b17bbd478d373304c67471d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,229 @@
using sc.modeling.splines.runtime;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Splines;
using UVC.Data.Core;
public enum conveyorType
{
partial,
overall
}
public class ConveyorComponent : ComponentBase
{
public List<ConveyorTarget> ConveyorQueue = new List<ConveyorTarget>();
public Stack<ConveyorTarget> WaitingQueue = new Stack<ConveyorTarget>();
public List<SplineMesher> mesher = new List<SplineMesher>();
SplineContainer spline;
float duration = 3f;
// === 추가: 슬롯 기반 제어 ===
public conveyorType type = conveyorType.overall;
public int segmentCount = 10; // 칸 개수
public float borderEpsilon = 1e-4f; // 경계 떨림 방지
bool[] occupancy; // 프레임 점유 캐시
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
spline = GetComponent<SplineContainer>();
occupancy = new bool[Mathf.Max(1, segmentCount)];
EntityManager.Instance.OnEntityDestroyed += UnSetEntity;
}
// Update is called once per frame
void Update()
{
/*
if (Input.GetKeyDown(KeyCode.G))
{
GameObject target = GameObject.CreatePrimitive(PrimitiveType.Cube);
var cTraget = target.AddComponent<ConveyorTarget>();
cTraget.Progress = 0f;
ConveyorQueue.Add(cTraget);
}
if (Input.GetKeyDown(KeyCode.S))
{
// 출구에서 한 개 배출
if (WaitingQueue.Count > 0)
{
var target = WaitingQueue.Pop();
Destroy(target.gameObject);
}
}
if (type == conveyorType.overall)
{
if (WaitingQueue.Count < 1)
{
SetProgress(Time.deltaTime / duration);
MoveOnConveyor();
}
}
else // partial
{
SetProgress(Time.deltaTime / duration);
MoveOnConveyor();
}
*/
SetProgress(Time.deltaTime / duration);
MoveOnConveyor();
}
void SetConveyorEntity(Entity entity)
{
var cTarget = entity.gameObject.GetComponent<ConveyorTarget>();
cTarget.transform.SetParent(this.transform, worldPositionStays: false);
cTarget.Progress = 0f;
ConveyorQueue.Add(cTarget);
}
public void SetModelData(DataObject datas)
{
var data = datas.GetDataObject("data");
var entityId = data.GetString("entity_id_original");
var entity = EntityManager.Instance.GetEntity(entityId,this);
if (entity == null)
{
Debug.Log(entityId);
Debug.Log(entity);
}
duration = (float)data.GetFloat("travel_time");
SetConveyorEntity(entity);
}
public void UnSetEntity(Entity entity)
{
var cTarget = entity.gameObject.GetComponent<ConveyorTarget>();
if (ConveyorQueue.Contains(cTarget))
{
ConveyorQueue.Remove(cTarget);
}
}
public void UnSetModelData(DataObject datas)
{
var data = datas.GetDataObject("data");
var entityId = data.GetString("entity_id_original");
var entity = EntityManager.Instance.GetEntity(entityId,this);
var cTarget = entity.gameObject.GetComponent<ConveyorTarget>();
if (ConveyorQueue.Contains(cTarget))
{
ConveyorQueue.Remove(cTarget);
}
}
void SetProgress(float progress)
{
if (ConveyorQueue.Count == 0) return;
if (type == conveyorType.overall)
{
// 기존: 전 화물 동일 진행
foreach (var target in ConveyorQueue)
{
target.Progress += progress;
if (target.Progress > 1f) target.Progress = 1f;
}
}
else
{
// 슬롯 기반: 끝 칸이 막혀 있어도 압축 전진
PartialAdvance(progress);
}
}
void MoveOnConveyor()
{
Spline s = spline.Splines[0];
for (int i = ConveyorQueue.Count - 1; i >= 0; i--)
{
var target = ConveyorQueue[i];
Vector3 localPos = SplineUtility.EvaluatePosition(s, target.Progress);
target.transform.position = localPos;
target.transform.position += new Vector3(0, 1f, -0.275f);
// 끝(1.0) 도달 → WaitingQueue로 이동(배출 대기)
if (target.Progress >= 1f)
{
ConveyorQueue.RemoveAt(i);
WaitingQueue.Push(target);
}
}
}
// === 한 칸씩 채우는 압축 로직 ===
void PartialAdvance(float delta)
{
// 끝에 가까운 순서로 처리 → 뒤따르는 한계 계산이 정확
ConveyorQueue.Sort((a, b) => b.Progress.CompareTo(a.Progress));
int cellCount = Mathf.Max(1, segmentCount);
float cell = 1f / cellCount;
if (occupancy == null || occupancy.Length != cellCount)
occupancy = new bool[cellCount];
System.Array.Clear(occupancy, 0, occupancy.Length);
// ★ 중요: 출구가 막혀 있으면 마지막 칸을 항상 점유된 것으로 간주
if (WaitingQueue.Count > 0)
occupancy[cellCount - 1] = true;
foreach (var t in ConveyorQueue)
{
float desired = Mathf.Min(1f, t.Progress + delta);
int currCell = CellIndex(t.Progress, cellCount);
int wantCell = CellIndex(desired, cellCount);
int allowedCell = currCell;
// 앞칸이 비어 있으면 한 칸 더, 또 비어 있으면 또 한 칸… 식으로 최대 전진
for (int c = currCell + 1; c <= wantCell && c < cellCount; c++)
{
if (occupancy[c]) break;
allowedCell = c;
}
// 허용 칸의 끝 경계 직전까지
float maxProg = Mathf.Min(desired, CellEnd(allowedCell, cellCount) - borderEpsilon);
// 마지막 칸이면 1.0까지 허용(도달 시 MoveOnConveyor에서 대기열로 이동)
if (allowedCell == cellCount - 1)
maxProg = Mathf.Min(desired, 1f);
t.Progress = Mathf.Clamp01(maxProg);
// 점유 표시
int newCell = CellIndex(t.Progress, cellCount);
occupancy[newCell] = true;
}
}
int CellIndex(float progress, int cellCount)
{
float cell = 1f / cellCount;
int idx = Mathf.FloorToInt(progress / cell);
return Mathf.Clamp(idx, 0, cellCount - 1);
}
float CellEnd(int cellIndex, int cellCount)
{
float cell = 1f / cellCount;
return (cellIndex + 1) * cell;
}
void UpdateKnot(BezierKnot knot, int index, int splineIndex)
{
spline.Splines[splineIndex].SetKnot(index, knot);
}
void UpdateMesh()
{
foreach (var m in mesher)
{
m.Rebuild();
}
}
}

View File

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

View File

@@ -0,0 +1,217 @@
using sc.modeling.splines.runtime;
using Simulator.Data;
using Simulator.Data.Transport;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Splines;
using UVC.Core;
using UVC.Data;
using UVC.Data.Core;
using UVC.Data.Mqtt;
public class ConveyorPathMeta : MonoBehaviour
{
public string Name;
public string From;
public string To;
public float Speed;
public float Length;
public int Capacity;
}
public class ConveyorManager : SingletonScene<ConveyorManager>
{
[Header("Root & Visual")]
[SerializeField] private Transform rootParent; // 생성될 경로의 부모(없으면 빌더 자신)
[SerializeField] private float tangentScale = 0.25f; // 곡률(탄젠트 길이 비율)
[SerializeField]
ConveyorComponent splineprefab;
[SerializeField]
GameObject cube;
public Dictionary<string, ConveyorNode> Nodes = new Dictionary<string, ConveyorNode>();
public Dictionary<(string,string), ConveyorComponent> Paths = new Dictionary<(string, string), ConveyorComponent>();
public DataMapper conveyorStartDataMapper;
public DataMapper conveyorCompleteDataMapper;
protected override void Init()
{
var conveyorStartDataMask = new DataMask();
conveyorStartDataMask.ObjectName = "conveyor"; // AGV 객체의 이름을 설정합니다.
conveyorStartDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
conveyorStartDataMask["component_id"] = "";
conveyorStartDataMask["event_name"] = "";
conveyorStartDataMask["timestamp"] = new DateTime();
conveyorStartDataMask["data"] = new DataMask()
{
["entity_id_original"] = "",
["start_node"] = "",
["end_node"] = "",
["travel_time"]=0.0f
};
conveyorStartDataMapper=new DataMapper(conveyorStartDataMask);
var conveyorCompleteDataMask = new DataMask();
conveyorCompleteDataMask.ObjectName = "conveyor"; // AGV 객체의 이름을 설정합니다.
conveyorCompleteDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
conveyorCompleteDataMask["component_id"] = "";
conveyorCompleteDataMask["event_name"] = "";
conveyorCompleteDataMask["timestamp"] = new DateTime();
conveyorCompleteDataMask["data"] = new DataMask()
{
["entity_id_original"] = "",
["start_node"] = "",
["end_node"] = "",
};
conveyorCompleteDataMapper = new DataMapper(conveyorCompleteDataMask);
}
public void Build(List<ConveyorDataClass> datas)
{
if (datas==null||datas.Count <= 0)
{
return;
}
var data = datas[0];
if (rootParent == null) rootParent = transform;
if (data.nodes != null)
{
foreach (var n in data.nodes)
{
Nodes[n.name] = n;
}
}
foreach (var path in data.paths)
{
CreatePathGOWithSpline(path, Nodes[path.from_node], Nodes[path.to_node]);
}
SpawnJunction();
SubscribeConveyor(data);
}
public void SubscribeConveyor(ConveyorDataClass data)
{
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_started");
var mqttConfigConveyorStart = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_started");
mqttConfigConveyorStart.SetDataMapper(conveyorStartDataMapper);
mqttConfigConveyorStart.SetHandler(OnSetData);
DataRepository.Instance.MqttReceiver.Add(mqttConfigConveyorStart);
/*
DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_completed");
var mqttConfigConveyorComplete = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_completed");
mqttConfigConveyorComplete.SetDataMapper(conveyorCompleteDataMapper);
mqttConfigConveyorComplete.SetHandler(OnUnSetData);
DataRepository.Instance.MqttReceiver.Add(mqttConfigConveyorComplete);
*/
}
private void CreatePathGOWithSpline(ConveyorPath path, ConveyorNode fromNode, ConveyorNode toNode)
{
TryGetNodePos(fromNode.name, out Vector3 fromPos);
TryGetNodePos(toNode.name, out Vector3 toPos);
string goName = path.name;
var go = Instantiate(splineprefab);
Paths.Add((fromNode.name,toNode.name), go);
go.transform.SetParent(rootParent, worldPositionStays: false);
var container = go.GetComponent<SplineContainer>();
var spline = new Spline();
Vector3 dir = (toPos - fromPos);
float dist = dir.magnitude;
Vector3 dirNorm = dist > 1e-5f ? dir / dist : Vector3.forward;
Vector3 tan = dirNorm * dist * Mathf.Clamp01(tangentScale);
var k0 = new BezierKnot(fromPos)
{
TangentOut = tan,
TangentIn = Vector3.zero
};
var k1 = new BezierKnot(toPos)
{
TangentIn = -tan,
TangentOut = Vector3.zero
};
spline.Add(k0, TangentMode.Broken);
spline.Add(k1, TangentMode.Broken);
container.Spline = spline;
var mesher = go.GetComponent<SplineMesher>();
if(string.Equals(fromNode.node_type, "junction"))
{
mesher.DetachCap(0);
}
if (string.Equals(toNode.node_type, "junction"))
{
mesher.DetachCap(1);
}
mesher.UpdateCaps();
mesher.Rebuild();
}
private void SpawnJunction()
{
foreach(var node in Nodes)
{
if (string.Equals(node.Value.node_type, "junction"))
{
var module = Instantiate(cube);
module.transform.position=ToVector3(node.Value.position);
}
}
}
private void OnSetData(IDataObject idata)
{
if (idata == null) return;
DataObject? obj = idata as DataObject;
if (obj == null) return;
//Debug.Log($"OnUpdateData:{obj}, {obj["data"]}");
var data = obj.GetDataObject("data");
var fromNodeName = data.GetString("start_node");
var toNodeName = data.GetString("end_node");
ConveyorComponent? conveyor = Paths[(fromNodeName,toNodeName)];
conveyor.SetModelData(obj);
}
private void OnUnSetData(IDataObject idata)
{
if (idata == null) return;
DataObject? obj = idata as DataObject;
if (obj == null) return;
//Debug.Log($"OnUpdateData:{obj}, {obj["data"]}");
var data = obj.GetDataObject("data");
var fromNodeName = data.GetString("start_node");
var toNodeName = data.GetString("end_node");
ConveyorComponent? conveyor = Paths[(fromNodeName, toNodeName)];
conveyor.UnSetModelData(obj);
}
private bool TryGetNodePos(string nodeName, out Vector3 pos)
{
if (!string.IsNullOrWhiteSpace(nodeName) && Nodes.TryGetValue(nodeName, out ConveyorNode node))
{
pos = ToVector3(node.position);
return true;
}
pos = default;
return false;
}
private Vector3 ToVector3(Position p)
{
return new Vector3(p.x, p.z, p.y);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 677f767b766c8c14b90b1ea42b7b89fe

View File

@@ -0,0 +1,6 @@
using UnityEngine;
public class ConveyorTarget : MonoBehaviour
{
public float Progress = 0f;
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d071f7d2c7896784e9ba89d0e2cfd860
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System;
using UnityEngine;
namespace Simulator.Data
{
[Serializable]
public class ConveyorDataClass
{
public string name;
public List<ConveyorNode> nodes;
public List<ConveyorPath> paths;
}
[Serializable]
public class ConveyorNode
{
public string name;
public int capacity;
public Position position;
public string node_type;
}
[Serializable]
public class ConveyorPath
{
public string name;
public float speed;
public float length;
public string to_node;
public int capacity;
public string from_node;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 556ab1c161f61e147b353f5e5770feee

View File

@@ -0,0 +1,59 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Splines;
public class SplineTarget : MonoBehaviour
{
public BezierKnot knot;
public event Action<BezierKnot, int, int> onKnotChanged;
public event Action onChangeFinished;
bool isClick;
public int index;
public int splineIndex;
// Update is called once per frame
void Update()
{
/*
if (isClick)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
transform.position = hit.point + new Vector3(0, 2, 0);
knot.Position = transform.position - new Vector3(0, 2, 0);
onKnotChanged?.Invoke(knot, index, splineIndex);
}
}
*/
}
public void Init(BezierKnot knot, int index, int splineIndex = 0)
{
this.knot = knot;
this.index = index;
this.splineIndex = splineIndex;
}
/*
public void OnPointerDown(PointerEventData eventData)
{
isClick = true;
transform.GetComponent<Collider>().enabled = false;
}
public void OnPointerUp(PointerEventData eventData)
{
isClick = false;
transform.GetComponent<Collider>().enabled = true;
onChangeFinished?.Invoke();
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("click");
}
*/
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 20514b0c6cbc9f14e9bb98a1734bdd22

View File

@@ -8,5 +8,6 @@ namespace Simulator.Data
{
public string name;
public string label;
public rack_layout asrs_layout;
}
}

View File

@@ -3,6 +3,6 @@ namespace Simulator.Data
{
public class ComponentDataBase
{
public physical physical;
public Physical physical;
}
}

View File

@@ -9,9 +9,7 @@ namespace Simulator.Data
public string name;
public string label;
public int capacity;
public bool is_unlimited;
public string policy;
public float wait_cost;
public float cost_time;
public string queue_type;
public bool backpressure_enabled;
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System;
using UnityEngine;
namespace Simulator.Data
{
[Serializable]
public class RackDataClass : ComponentDataBase
{
public string name;
public string label;
//public List<string> inputs=new List<string>();
//public List<string> outputs=new List<string>();
public rack_layout rack_layout=new rack_layout();
public string connected_input_node;
}
[Serializable]
public class rack_layout
{
public float x;
public float y;
public float z;
public float x_length;
public float y_length;
public float z_length;
}
}

View File

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

View File

@@ -1,57 +1,95 @@
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Asn1.Mozilla;
using Best.HTTP.SecureProtocol.Org.BouncyCastle.Bcpg;
using Newtonsoft.Json;
using Simulator.Data.Transport;
using System;
using System.Collections.Generic;
namespace Simulator.Data
{
[Serializable]
public class totaljson
public class Totaljson
{
public int status;
public string code;
public logicData data;
public LogicData data;
}
[Serializable]
public class logicData
public class LogicData
{
public int id;
public logicDetailData data;
public LogicDetailData data;
}
[Serializable]
public class logicDetailData
public class LogicDetailData
{
public string name;
public infrastructure infrastructure;
public production_system production_system;
public Infrastructure infrastructure;
public Transport_System transport_system;
public Production_System production_system;
}
[Serializable]
public class infrastructure
public class Infrastructure
{
public List<QueueDataClass> queues;
}
[Serializable]
public class production_system
public class Transport_System
{
public List<Node_Networks> node_networks=new List<Node_Networks>();
public List<Transport_Manager> transport_managers=new List<Transport_Manager>();
}
[Serializable]
public class Node_Networks
{
public List<NodeDataClass> nodes= new List<NodeDataClass>();
public List<PathDataClass> paths=new List<PathDataClass>();
}
[Serializable]
public class Transport_Manager
{
public string label;
public string manager_id;
public string network_id;
public string manager_type;
public List<Vehicle_Fleet> vehicle_fleets;
}
[Serializable]
public class Vehicle_Fleet
{
public string label;
public string fleet_id;
public List<AGVData> vehicles;
}
[Serializable]
public class Production_System
{
public List<SourceDataClass> sources;
public List<SinkDataClass> sinks;
public List<ASRSDataClass> asrs;
public List<RackDataClass> racks;
public List<ConveyorDataClass> conveyors;
}
[Serializable]
public class physical
public class Physical
{
public position position = new position();
public dimensions dimensions = new dimensions();
public Position position = new Position();
public Dimensions dimensions = new Dimensions();
public float orientation;
public access_points access_points = new access_points();
public Access_Points access_points = new Access_Points();
}
[Serializable]
public class position
public class Position
{
public float x = 0.0f;
public float y = 0.0f;
@@ -59,7 +97,7 @@ namespace Simulator.Data
}
[Serializable]
public class dimensions
public class Dimensions
{
public float width = 0.0f;
public float height = 0.0f;
@@ -67,21 +105,21 @@ namespace Simulator.Data
}
[Serializable]
public class access_points
public class Access_Points
{
public input input;
public output output;
public Input input;
public Output output;
}
[Serializable]
public class input
public class Input
{
public position local_position = new position();
public Position local_position = new Position();
}
[Serializable]
public class output
public class Output
{
public position local_position = new position();
public Position local_position = new Position();
}
}

View File

@@ -0,0 +1,38 @@
using Simulator.Model;
using System;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UVC.Data.Core;
namespace Simulator.Data
{
public class QueueComponent : ComponentBase
{
QueueDataClass queueData;
public GameObject entitySocket;
public void SetComponent(QueueDataClass queueData)
{
this.queueData = queueData;
data = queueData;
}
public override void GetModelData(DataObject modelData)
{
Debug.Log("실행되어선 안되는 함수입니다");
}
public void PlaceEntity(Entity entity)
{
if (entitySocket == null)
{
return;
}
entity.transform.SetParent(entitySocket.transform, worldPositionStays: false);
float stepZ = EntityManager.Instance.ObjectSize.z;
entity.transform.localPosition = new Vector3(0f, 0f, 0f);
entity.transform.localRotation = Quaternion.identity;
}
}
}

View File

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

View File

@@ -0,0 +1,72 @@
using Simulator.Model;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UVC.Data.Core;
namespace Simulator.Data {
public class RackComponent : ComponentBase
{
public GameObject cellPrefab;
public RackDataClass rackData;
public Dictionary<(int x,int y, int z),CellComponent> cellComponents=new Dictionary<(int x, int y, int z), CellComponent>();
public List<GameObject> entitys=new List<GameObject>();
public void SetComponent(RackDataClass data)
{
this.data = data;
rackData= data;
SpawnCell(data.rack_layout,false);
}
public override void GetModelData(DataObject modelData)
{
var datas = modelData.GetDataObject("data");
var entityid = datas.GetString("entity_id");
List<string> entityids = new List<string>() { entityid };
var coordinates = datas.GetDataObject("coordinates");
var x = (int)coordinates.GetInt("x");
var y = (int)coordinates.GetInt("y");
var z = (int)coordinates.GetInt("z");
var entity=EntityManager.Instance.GetEntities(entityids,this);
entity[0].transform.SetParent(cellComponents[(x, z, y)].Socket.transform);
entity[0].transform.localPosition = new Vector3(0, 0, 0);
entitys.Add(entity[0].gameObject);
}
public void SpawnCell(rack_layout layout,bool asrs)
{
Vector3 center = new Vector3(layout.x * layout.x_length / 2f, 0f, layout.y * layout.y_length / 2f);
transform.position = center;
for (int z = 0; z < layout.z; z++)
{
for(int y=0;y<layout.y; y++)
{
for(int x=0;x<layout.x; x++)
{
var cell = Instantiate(cellPrefab);
cell.transform.localScale=new Vector3(layout.x_length, layout.z_length, layout.y_length);
cell.transform.parent = this.transform;
cell.transform.position = new Vector3(layout.x_length * x*0.55f, layout.z_length * z * 0.55f, layout.y_length * y * 0.55f);
cellComponents.Add((x, z, y), cell.GetComponent<CellComponent>());
}
}
}
if (asrs)
{
var pickcell = Instantiate(cellPrefab);
pickcell.transform.localScale = new Vector3(layout.x_length, layout.z_length, layout.y_length);
pickcell.transform.parent = this.transform;
pickcell.transform.position = new Vector3(layout.x_length * -0.55f, layout.z_length * 0, layout.y_length * 0);
cellComponents.Add((-1, 0, 0), pickcell.GetComponent<CellComponent>());
var dropcell = Instantiate(cellPrefab);
dropcell.transform.localScale = new Vector3(layout.x_length, layout.z_length, layout.y_length);
dropcell.transform.parent = this.transform;
dropcell.transform.position = new Vector3(layout.x_length * (layout.x+ 0.55f), layout.z_length * 0, layout.y_length * 0);
cellComponents.Add(((int)(layout.x + 1), 0, 0), dropcell.GetComponent<CellComponent>());
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 10cc8432a692c434b96251d427314548

View File

@@ -2,12 +2,14 @@ using Simulator.Data;
using Simulator.Model;
using UnityEngine;
using UVC.Data.Core;
using System.Collections.Generic;
using Unity.VisualScripting;
public class SinkComponent : ComponentBase
{
public SinkDataClass sourceData = new SinkDataClass();
public SinkModelData sinkModelData = new SinkModelData();
public void ChangeComponent(SinkDataClass sourceData)
public void SetComponent(SinkDataClass sourceData)
{
this.sourceData = sourceData;
data = sourceData;
@@ -15,6 +17,9 @@ public class SinkComponent : ComponentBase
public override void GetModelData(DataObject modelData)
{
sinkModelData.component_id=modelData.GetString("component_id");
sinkModelData.component_id = modelData.GetString("component_id");
var datas = modelData.GetDataObject("data");
var sinkdata = datas["entity_ids"].ConvertTo<List<string>>();
EntityManager.Instance.DestroyEnity(sinkdata);
}
}

View File

@@ -1,13 +1,19 @@
using Simulator.Data;
using Simulator.Model;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UVC.Data.Core;
public class SourceComponent : ComponentBase
{
public SourceDataClass sourceData=new SourceDataClass();
public SourceModelData sourceModelData=new SourceModelData();
SourceDataClass sourceData=new SourceDataClass();
SourceModelData sourceModelData=new SourceModelData();
public List<GameObject> entitySocket;
List<Entity> possessEntities=new List<Entity>();
public int count=0;
public int index = 0;
public void SetComponent(SourceDataClass sourceData)
{
@@ -15,8 +21,109 @@ public class SourceComponent : ComponentBase
data = sourceData;
}
void IncreaseCount()
{
count++;
if (count >= entitySocket.Count)
{
index++;
count = 0;
}
}
void PlaceNext(Entity entity)
{
if (entitySocket == null || entitySocket.Count == 0)
{
Debug.LogWarning("[SourceComponent] entitySocket이 비어있습니다. 배치를 건너뜁니다.");
return;
}
// 현재 소켓이 비어있으면 다음 유효 소켓을 찾는다(안전장치)
int guard = 0;
while ((entitySocket[count] == null) && guard < entitySocket.Count)
{
IncreaseCount();
guard++;
}
if (entitySocket[count] == null)
{
Debug.LogWarning("[SourceComponent] 사용 가능한 소켓이 없습니다. 배치를 건너뜁니다.");
return;
}
// 부모/로컬 트랜스폼 설정
entity.transform.SetParent(entitySocket[count].transform, worldPositionStays: false);
// Z방향으로 적층(원래 로직 유지)
float stepZ = EntityManager.Instance.ObjectSize.z;
entity.transform.localPosition = new Vector3(0f, 0f, stepZ * index);
entity.transform.localRotation = Quaternion.identity;
if (!possessEntities.Contains(entity))
possessEntities.Add(entity);
IncreaseCount();
}
public override void GetModelData(DataObject modelData)
{
sourceModelData.total_entity = modelData.GetDataObject("data").GetInt("total_entity")??0;
// total_entity 업데이트(참조용)
sourceModelData.total_entity = modelData.GetDataObject("data").GetInt("total_entity") ?? 0;
// entity_ids -> 여러 개 동시 스폰
var datas = modelData.GetDataObject("data");
var entityIds = datas["entity_ids"].ConvertTo<List<string>>();
if (entityIds == null || entityIds.Count == 0)
{
Debug.LogWarning("[SourceComponent] entity_ids가 비어있습니다.");
return;
}
// 여러 개를 한 번에 스폰
var entities = EntityManager.Instance.SpawnEntites(entityIds,this);
if (entities == null || entities.Count == 0)
{
return;
}
// 배치
foreach (var e in entities)
{
if (e == null) continue;
PlaceNext(e);
}
// (선택) 총계 갱신
// sourceModelData.total_entity += entities.Count; // 필요 시 해제
}
public override void DecreaseEntity(Entity entity)
{
possessEntities.Remove(entity);
ReflowEntities();
}
public void ReflowEntities()
{
// 위치 인덱스 초기화
count = 0;
index = 0;
// 🔑 핵심: 스냅샷을 만든 뒤 원본을 비우고, 스냅샷 기반으로 재배치
// 이렇게 하면 순회 중 리스트 수정으로 인한 예외가 발생하지 않습니다.
var snapshot = new List<Entity>(possessEntities.Count);
foreach (var e in possessEntities)
{
if (e != null) snapshot.Add(e);
}
possessEntities.Clear();
foreach (var e in snapshot)
{
PlaceNext(e); // 여기서 다시 possessEntities에 안전하게 채워짐
}
}
}