@fmsim/machine 2.0.0-beta.85 → 2.0.0-beta.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,18 @@
1
1
  import { themesColorMap, themesAnimationMap } from '@fmsim/api'
2
2
  import MCSUnit from './mcs-unit.js'
3
3
 
4
+ /**
5
+ * Holder.data 변경 감지 시 carrierManager 에 전달할 4 키 묶음 + ADDITIONS 배열.
6
+ * 비교 결과 단 하나라도 다르면 carrierManager.update 호출.
7
+ */
8
+ const CARRIER_DATA_KEYS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const
9
+
10
+ function additionsChanged(a: any, b: any): boolean {
11
+ // shallow JSON 비교로 충분 — 각 항목이 4 키 primitive 만 가짐. 깊은 ref 비교
12
+ // 보다 빠르고 정확. 빈/누락은 같은 '[]' 직렬화로 동치 취급.
13
+ return JSON.stringify(a?.ADDITIONS ?? []) !== JSON.stringify(b?.ADDITIONS ?? [])
14
+ }
15
+
4
16
  export default class MCSCarrierHolder extends MCSUnit {
5
17
  static get properties(): any {
6
18
  return [
@@ -26,6 +38,8 @@ export default class MCSCarrierHolder extends MCSUnit {
26
38
  label: 'show-carrier-text',
27
39
  name: 'showCarrierText'
28
40
  }
41
+ // 적층 두께는 carrier 측 (Carrier.added) 에서 자기 default depth 를 결정.
42
+ // holder 측에는 별도 carrierDepth 속성을 두지 않는다.
29
43
  ]
30
44
  }
31
45
 
@@ -48,39 +62,38 @@ export default class MCSCarrierHolder extends MCSUnit {
48
62
  async ready() {
49
63
  super.ready()
50
64
 
51
- const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}
52
-
53
- if(CARRIERNAME) {
54
- ;(this.root as any).carrierManager.update(this, {
55
- CARRIERNAME,
56
- CARRIERTYPE,
57
- EMPTYTYPE,
58
- CARRIERSTATUS
59
- }, undefined)
65
+ if (this._hasCarrierData(this.data)) {
66
+ ;(this.root as any).carrierManager.update(this, this.data || {}, undefined)
60
67
  }
61
68
  }
62
69
 
63
70
  onchangeData(after: Record<string, any>, before: Record<string, any>): void {
64
- // CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS 이 변경되었을 때만 캐리어 관리자에게 알린다.
65
- const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = after.data || {}
66
- const { CARRIERNAME: beforeCARRIERNAME, CARRIERTYPE: beforeCARRIERTYPE = '', EMPTYTYPE: beforeEMPTYTYPE, CARRIERSTATUS: beforeCARRIERSTATUS } = before.data || {}
67
- if(CARRIERNAME !== beforeCARRIERNAME ||
68
- CARRIERTYPE !== beforeCARRIERTYPE ||
69
- EMPTYTYPE !== beforeEMPTYTYPE ||
70
- CARRIERSTATUS !== beforeCARRIERSTATUS) {
71
- ;(this.root as any).carrierManager.update(this, {
72
- CARRIERNAME,
73
- CARRIERTYPE,
74
- EMPTYTYPE,
75
- CARRIERSTATUS
76
- }, {
77
- CARRIERNAME: beforeCARRIERNAME,
78
- CARRIERTYPE: beforeCARRIERTYPE,
79
- EMPTYTYPE: beforeEMPTYTYPE,
80
- CARRIERSTATUS: beforeCARRIERSTATUS
81
- })
71
+ const afterData = after.data || {}
72
+ const beforeData = before.data || {}
73
+
74
+ let changed = false
75
+ for (const k of CARRIER_DATA_KEYS) {
76
+ if (afterData[k] !== beforeData[k]) {
77
+ changed = true
78
+ break
79
+ }
80
+ }
81
+ if (!changed) {
82
+ changed = additionsChanged(afterData, beforeData)
83
+ }
84
+
85
+ if (changed) {
86
+ ;(this.root as any).carrierManager.update(this, afterData, beforeData)
82
87
  }
83
88
 
84
89
  super.onchangeData(after, before)
85
90
  }
91
+
92
+ /** data 안에 carrier 정보 (대표 또는 ADDITIONS) 가 하나라도 있는지. */
93
+ private _hasCarrierData(data: any): boolean {
94
+ if (!data) return false
95
+ if (data.CARRIERNAME) return true
96
+ if (Array.isArray(data.ADDITIONS) && data.ADDITIONS.some((a: any) => a?.CARRIERNAME)) return true
97
+ return false
98
+ }
86
99
  }
@@ -249,124 +249,59 @@ ModelLayer.prototype.onchange = function (after: any) {
249
249
  return _origModelLayerOnchange.call(this, after)
250
250
  }
251
251
 
252
- Object.defineProperty(RootContainer.prototype, 'equipmentLegendTheme', {
253
- get: function () {
254
- const { equipmentLegendName } = this.rootModel.state
255
- return equipmentLegendName && this.state.style[equipmentLegendName]
256
- }
257
- })
258
-
259
- Object.defineProperty(RootContainer.prototype, 'bufferLegendTheme', {
260
- get: function () {
261
- const { bufferLegendName } = this.rootModel.state
262
- return bufferLegendName && this.state.style[bufferLegendName]
263
- }
264
- })
265
-
266
- Object.defineProperty(RootContainer.prototype, 'agvlineLegendTheme', {
267
- get: function () {
268
- const { agvlineLegendName } = this.rootModel.state
269
- return agvlineLegendName && this.state.style[agvlineLegendName]
270
- }
271
- })
272
-
273
- Object.defineProperty(RootContainer.prototype, 'ohtlineLegendTheme', {
274
- get: function () {
275
- const { ohtlineLegendName } = this.rootModel.state
276
- return ohtlineLegendName && this.state.style[ohtlineLegendName]
277
- }
278
- })
279
-
280
- Object.defineProperty(RootContainer.prototype, 'conveyorLegendTheme', {
281
- get: function () {
282
- const { conveyorLegendName } = this.rootModel.state
283
- return conveyorLegendName && this.state.style[conveyorLegendName]
284
- }
285
- })
286
-
287
- Object.defineProperty(RootContainer.prototype, 'stokerLegendTheme', {
288
- get: function () {
289
- const { stokerLegendName } = this.rootModel.state
290
- return stokerLegendName && this.state.style[stokerLegendName]
291
- }
292
- })
293
-
294
- Object.defineProperty(RootContainer.prototype, 'agvLegendTheme', {
295
- get: function () {
296
- const { agvLegendName } = this.rootModel.state
297
- return agvLegendName && this.state.style[agvLegendName]
298
- }
299
- })
300
-
301
- Object.defineProperty(RootContainer.prototype, 'ohtLegendTheme', {
302
- get: function () {
303
- const { ohtLegendName } = this.rootModel.state
304
- return ohtLegendName && this.state.style[ohtLegendName]
305
- }
306
- })
307
-
308
- Object.defineProperty(RootContainer.prototype, 'shuttleLegendTheme', {
309
- get: function () {
310
- const { shuttleLegendName } = this.rootModel.state
311
- return shuttleLegendName && this.state.style[shuttleLegendName]
312
- }
313
- })
314
-
315
- Object.defineProperty(RootContainer.prototype, 'craneLegendTheme', {
316
- get: function () {
317
- const { craneLegendName } = this.rootModel.state
318
- return craneLegendName && this.state.style[craneLegendName]
319
- }
320
- })
321
-
322
- Object.defineProperty(RootContainer.prototype, 'shelfLegendTheme', {
323
- get: function () {
324
- const { shelfLegendName } = this.rootModel.state
325
- return shelfLegendName && this.state.style[shelfLegendName]
326
- }
327
- })
328
-
329
- Object.defineProperty(RootContainer.prototype, 'portLegendTheme', {
330
- get: function () {
331
- const { portLegendName } = this.rootModel.state
332
- return portLegendName && this.state.style[portLegendName]
333
- }
334
- })
335
-
336
- Object.defineProperty(RootContainer.prototype, 'carrierLegendTheme', {
337
- get: function () {
338
- const { carrierLegendName } = this.rootModel.state
339
- return carrierLegendName && this.state.style[carrierLegendName]
340
- }
341
- })
342
-
343
- Object.defineProperty(RootContainer.prototype, 'carrierAnimationTheme', {
344
- get: function () {
345
- const { carrierAnimationName } = this.rootModel.state
346
- return carrierAnimationName && this.state.style[carrierAnimationName]
347
- }
348
- })
252
+ // === LegendTheme / AnimationTheme value resolve ===
253
+ //
254
+ // rootModel.state.style[name] 에 저장된 legend/animation 값은 보드 직렬화
255
+ // 형태에 따라 (a) 정상 object 또는 (b) JSON 문자열 둘 다로 나타난다 (2026-06-09
256
+ // 진단 — consumer 인 carrier.ts:animationPreset 이 같은 이유로 typeof string
257
+ // 분기 + JSON5.parse 처리하는 선례 존재). LegendTheme getter 들이 이 분기를
258
+ // 두지 않아 string 통과 → getValueOnRanges 매칭 실패 → 3D gauge 의 색상이
259
+ // fallback 으로만 결정되던 증상.
260
+ //
261
+ // fix 는 getter 한 곳에서 lazy parse. WeakMap<state, Map<rawString, parsed>>
262
+ // 으로 *raw string 자체를 key* — 보드 편집으로 raw 가 바뀌면 자연 무효화,
263
+ // 같은 raw 의 반복 lookup 은 O(1). root 가 GC 되면 cache 도 같이 GC.
264
+ const _legendParseCache = new WeakMap<object, Map<string, any>>()
349
265
 
350
- Object.defineProperty(RootContainer.prototype, 'stockerCapacityLegendTheme', {
351
- get: function () {
352
- const { stockerCapacityLegendName } = this.rootModel.state
353
- return stockerCapacityLegendName && this.state.style[stockerCapacityLegendName]
266
+ function parseLegendIfString(state: any, raw: any): any {
267
+ if (typeof raw !== 'string') return raw
268
+ let cache = _legendParseCache.get(state)
269
+ if (!cache) {
270
+ cache = new Map()
271
+ _legendParseCache.set(state, cache)
354
272
  }
355
- })
273
+ if (cache.has(raw)) return cache.get(raw)
274
+ let parsed: any
275
+ try { parsed = JSON.parse(raw) } catch { parsed = raw } // parse 실패 시 raw 보존
276
+ cache.set(raw, parsed)
277
+ return parsed
278
+ }
356
279
 
357
- Object.defineProperty(RootContainer.prototype, 'zoneCapacityLegendTheme', {
358
- get: function () {
359
- const { zoneCapacityLegendName } = this.rootModel.state
360
- return zoneCapacityLegendName && this.state.style[zoneCapacityLegendName]
280
+ function legendThemeGetter(stateKey: string) {
281
+ return function (this: any) {
282
+ const name = this.rootModel.state[stateKey]
283
+ const raw = name && this.state.style[name]
284
+ return parseLegendIfString(this.state, raw)
361
285
  }
362
- })
286
+ }
363
287
 
364
- Object.defineProperty(RootContainer.prototype, 'batteryRateLegendTheme', {
365
- get: function () {
366
- const { batteryRateLegendName } = this.rootModel.state
367
- return batteryRateLegendName && this.state.style[batteryRateLegendName]
368
- }
369
- })
288
+ Object.defineProperty(RootContainer.prototype, 'equipmentLegendTheme', { get: legendThemeGetter('equipmentLegendName') })
289
+ Object.defineProperty(RootContainer.prototype, 'bufferLegendTheme', { get: legendThemeGetter('bufferLegendName') })
290
+ Object.defineProperty(RootContainer.prototype, 'agvlineLegendTheme', { get: legendThemeGetter('agvlineLegendName') })
291
+ Object.defineProperty(RootContainer.prototype, 'ohtlineLegendTheme', { get: legendThemeGetter('ohtlineLegendName') })
292
+ Object.defineProperty(RootContainer.prototype, 'conveyorLegendTheme', { get: legendThemeGetter('conveyorLegendName') })
293
+ Object.defineProperty(RootContainer.prototype, 'stokerLegendTheme', { get: legendThemeGetter('stokerLegendName') })
294
+ Object.defineProperty(RootContainer.prototype, 'agvLegendTheme', { get: legendThemeGetter('agvLegendName') })
295
+ Object.defineProperty(RootContainer.prototype, 'ohtLegendTheme', { get: legendThemeGetter('ohtLegendName') })
296
+ Object.defineProperty(RootContainer.prototype, 'shuttleLegendTheme', { get: legendThemeGetter('shuttleLegendName') })
297
+ Object.defineProperty(RootContainer.prototype, 'craneLegendTheme', { get: legendThemeGetter('craneLegendName') })
298
+ Object.defineProperty(RootContainer.prototype, 'shelfLegendTheme', { get: legendThemeGetter('shelfLegendName') })
299
+ Object.defineProperty(RootContainer.prototype, 'portLegendTheme', { get: legendThemeGetter('portLegendName') })
300
+ Object.defineProperty(RootContainer.prototype, 'carrierLegendTheme', { get: legendThemeGetter('carrierLegendName') })
301
+ Object.defineProperty(RootContainer.prototype, 'carrierAnimationTheme', { get: legendThemeGetter('carrierAnimationName') })
302
+ Object.defineProperty(RootContainer.prototype, 'stockerCapacityLegendTheme', { get: legendThemeGetter('stockerCapacityLegendName') })
303
+ Object.defineProperty(RootContainer.prototype, 'zoneCapacityLegendTheme', { get: legendThemeGetter('zoneCapacityLegendName') })
304
+ Object.defineProperty(RootContainer.prototype, 'batteryRateLegendTheme', { get: legendThemeGetter('batteryRateLegendName') })
370
305
 
371
306
  Object.defineProperty(RootContainer.prototype, 'vehicleAnimation', {
372
307
  get: function () {