工业仿真(simulation)--发生器,吸收器,缓冲区(2)

在上一篇文章中,我们讲到了仿真引擎的设计,开发,实现。我们重点讲解了加工站实体类怎么接收产品,怎么加工,怎么派发产品

在这篇文章中,我们主要来讲一下发生器,吸收器,缓冲区的离散事件仿真,这三个实体类和加工站类一样,都需要继承BaseStation类

发生器-Generator.ts

1. 核心定义

发生器 是一个用于在仿真模型中按特定规则创建实体 的模块。它的主要作用是模拟到达过程,即实体在什么时间、以何种方式进入系统。

2. 设计思想

由于发生器继承了BaseStation类,但是发生器不需要接收产品的流入,只需要负责产生产品,派发产品,那么,在代码层面,我们就很好的进行开发了

  1. 首先是new一个产品
  2. 然后遍历下一站所有设备,查看哪个设备处于空闲状态
  3. 将产品派发给空闲的设备,最后设置一个事件,传入到eventQueue队列中

3. 代码开发

ts 复制代码
import { generateUUID } from '../../utils'
import { addReadyProduct } from '../core/ReadyProducts'
import { schedule } from '../core/Scheduler'
import { currentTime } from '../core/SimulationClock'
import { messageTransfer } from '../messaging/MessageTransfer'
import BaseStation from './BaseStation'
import Product from './Product'

class Generator extends BaseStation {
  productCounter = 0
  interval: number | string
  totalCount: number
  selfCirculation: boolean
  productWidth: number
  productHeight: number

  constructor(
    id: string,
    name: string,
    x: number,
    y: number,
    width = 100,
    height = 100,
    interval: number | string,
    totalCount: number,
    selfCirculation = true,
    productWidth: number = 20,
    productHeight: number = 20
  ) {
    super(id, name, x, y, width, height)
    this.interval = interval
    this.totalCount = totalCount
    this.selfCirculation = selfCirculation
    this.productWidth = productWidth
    this.productHeight = productHeight
  }

  start(): void {
    this.tryDispatchCurrentProduct(true)
  }

  scheduleNext(): void {
    if (this.totalCount !== -1 && this.productCounter >= this.totalCount) return
    let time = 0
    if (typeof this.interval === 'string') {
      time = new Function(this.interval)()
    } else {
      time = this.interval
    }
    schedule(time, () => this.tryDispatchCurrentProduct(true), `${this.id} 生成产品`)
  }

  canReceiveProduct(): boolean {
    return false
  }
  onProductReceived(): void {
    throw new Error('Generator cannot receive ready product')
  }

  tryDispatchCurrentProduct(outside = false): boolean {
    const intervalAllocation = (): void => {
      const product = new Product(generateUUID(), this.productWidth, this.productHeight)

      const availableStation = this.nextStations.find((s) => s.canReceiveProduct(this.id, product))
      if (availableStation === undefined) {
        console.log(`[${currentTime}] ⛔ ${this.name} 下游无空闲设备`)
        this.scheduleNext()
        //如果下游不接收,则删除本产品
        return
      }

      console.log(`[${currentTime}] 🚀 ${this.name} 产生 ${product.id}`)
      product.setFrom(this.id)
      messageTransfer('product', 'generate', { targetId: this.id, productId: product.id })
      // messageTransfer('product', 'startProcessing', { targetId: this.id, productId: product.id })

      //把产品交给就绪产品队列中
      addReadyProduct(product)
      availableStation.receiveReadyProduct(product.id)
      this.productCounter++
      if (this.totalCount !== -1) {
        messageTransfer('style', null, {
          targetId: this.id,
          style: {
            quantity: this.totalCount - this.productCounter
          }
        })
      }

      this.scheduleNext()
    }
    if (outside) {
      if (this.selfCirculation) {
        intervalAllocation()
        return false
      } else {
        console.log('正在开发')
        return false
      }
    } else {
      if (this.selfCirculation) {
        console.log('直接跳过')
        return false
      } else {
        console.log('正在开发')
        return false
      }
    }
  }
}

export default Generator

发生器有三个方法需要说明一下

  • start()发生器的启动方法,在仿真引擎开启后,需要在代码层面主动触发该方法
  • scheduleNext()用来处理事件循环往复,同时也处理time值,判断time值是固定值还是拟合函数
  • tryDispatchCurrentProduct()产品派发方法

tryDispatchCurrentProduct方法是怎么处理产品的,已经在设计思想里面讲过,那此时我们就会有一个疑问,start方法在哪里触发

这就需要我们回到我们的建模文件里面

ModelBuilder.ts

在上述代码,最下面,有一个开启发生器,也就是说我们在建完模后,就立即开启了发生器的start(),而这个modeling方法,则在仿真控制器里面运行

ts 复制代码
async start(): Promise<void> {
    //清空一下数据
    this.clearAll()
    if (this.status !== 'idle' && this.status !== 'stopped') return

    console.log('[Sim] ✅ 开始仿真')
    this.status = 'running'
    this.shouldStop = false
    let lastEventTime = 0
    modeling() // 初始化模型(只运行一次)
    while (!eventQueue.isEmpty()) {
      if (this.shouldStop) break
      if (this.status === ('paused' as SimStatus)) {
        await this.waitUntilResume()
      }

      const event = eventQueue.dequeue()
      if (event) {
        const simDelay = event.time - lastEventTime
        lastEventTime = event.time
        const waitMs = (simDelay * 1000) / SimulationSpeed.getSpeed
        await delay(waitMs)
        //发送仿真时间
        messageTransfer('simTime', null, event.time)
        setCurrentTime(event.time)
        event.action()
      }
    }

    if (this.status !== ('paused' as SimStatus)) {
      this.status = 'stopped'
      console.log('[Sim] 🛑 仿真完成或已停止')
    }
  }

到此我们的发生器就讲解完毕,发生器相对于加工站实体类来说,还是比较简单理解的,因为发生器不需要和上一站交互,只需要产生产品,然后将产品给下一站,并且也不需要转变状态

吸收器-Sink.ts

1. 核心定义

吸收器 是一个用于从仿真模型中移出已完成所有处理的实体 的模块。它的主要作用是模拟实体的离开过程 ,并在此刻收集关键的最终绩效数据。除此以外,吸收器还要负责收集最终统计数据

这是吸收器最重要的作用。当实体被吸收器销毁前,仿真引擎会在此刻记录该实体的各项数据,用于生成最终的性能报告。常见的数据包括:

  • 吞吐量:单位时间内离开系统的实体数量。这是衡量系统整体效率的核心指标。
  • 停留时间/周期时间 :一个实体从被发生器 创建到被吸收器移出的总时间。这直接反映了系统完成一个任务所需的时间。
  • 准时交货率:如果实体有"到期时间"属性,吸收器可以判断它是否延迟,从而统计准时率。
  • WIP(在制品)计算:通过"进入系统的实体总数"减去"离开系统的实体总数",可以随时计算出系统当前的WIP数量。

2. 设计思想

吸收器是发生器的反向面,也就是说只需要接收产品,销毁产品,那我们直接看代码

3. 代码开发

ts 复制代码
import { getReadyProduct } from '../core/ReadyProducts'
import { currentTime } from '../core/SimulationClock'
import { messageTransfer } from '../messaging/MessageTransfer'
import BaseStation from './BaseStation'
import Product from './Product'

class Sink extends BaseStation {
  receivedCount = 0

  constructor(id: string, name: string, x: number, y: number, width = 100, height = 100) {
    super(id, name, x, y, width, height)
  }

  //是否可以接受产品
  canReceiveProduct(): boolean {
    return true
  }

  // 接受就绪产品
  receiveReadyProduct(productId: string): void {
    this.receiveProduct(productId)
  }

  receiveProduct(productId: string): void {
    const product = getReadyProduct(productId)
    if (product) {
      messageTransfer('product', 'move', { targetId: this.id, productId: product.id })
      this.receivedCount++
      console.log(`[${currentTime}] ✅ ${product.id} 已被 ${this.name} 吸收`)
      messageTransfer('product', 'recycle', { targetId: this.id, productId: product.id })
    } else {
      console.log(`[${currentTime}] ❌ ${productId} 没有发现`)
    }
  }

  public onProductReceived(product: Product): void {
    return
  }

  public tryDispatchCurrentProduct(): void {
    return
  }
}

export default Sink

相对于发生器来说,代码似乎要简介不少,在canReceiveProduct()方法中,直接返回true, 表示永远都可以接收产品,然后拿到产品后,立即销毁

缓冲区-Buffer.ts

1. 核心定义

缓冲区 是一个用于临时存放实体 的模块,这些实体已经进入了系统但尚未被下游资源或工序处理。起到了临时存放作用, 实体不会在缓冲区中被加工或处理,它们只是在此处等待。

缓冲区的核心作用

a) 解耦生产工序

这是缓冲区最重要的作用。它像一个"减震器"或"蓄水池",隔离了相邻工序之间的相互影响。

  • 情景:前道工序(A)速度很快,后道工序(B)速度很慢。
  • 没有缓冲区:工序A一完成就必须交给工序B,但B还在忙,于是A被迫停止工作(阻塞),整个系统效率降低。
  • 有缓冲区:工序A完成的产品可以先放入缓冲区,然后A可以继续处理下一个任务。工序B在空闲时再从缓冲区中取货。这样保证了工序A不会因B的忙碌而停工。

b) 应对 variability(可变性)

真实生产中充满了不确定性(如设备随机故障、操作员效率波动、物料到达时间随机等)。缓冲区可以吸收这些波动,保证生产线的连续运行。

  • 情景:后道工序(B)的机器突然发生故障,需要维修1小时。
  • 没有缓冲区:工序A会立刻被阻塞,整个生产线停摆。
  • 有缓冲区:在B维修的1小时内,工序A生产的产品可以暂存在缓冲区中。只要缓冲区不满,A就能继续生产。这提高了系统的抗干扰能力和稳健性。

c) 维持资源利用率

通过防止上游工序因下游阻塞而停工,缓冲区确保了昂贵的生产资源(如机器、机器人)能够保持较高的利用率。

2. 设计思想

缓冲区和加工站类似,产品流入--等待--产品流入

  1. 当收到产品流入的请求时,先判断自己是否有足够的剩余空间,返回Boolean值
  2. 收到就绪产品后,立即触发派发产品方法
  3. 派发产品方法和加工站一样

3. 代码开发

ts 复制代码
import { currentTime } from '../core/SimulationClock'
import { messageTransfer } from '../messaging/MessageTransfer'
import Product from './Product'
import BaseStation from './BaseStation'
import { addReadyProduct, getReadyProduct } from '../core/ReadyProducts'

class BufferNode extends BaseStation {
  capacity: number
  queue: Product[] = []
  isDispatchScheduled: boolean = false // 防止重复调度

  constructor(
    id: string,
    name: string,
    x: number,
    y: number,
    width = 100,
    height = 100,
    capacity: number
  ) {
    super(id, name, x, y, width, height)
    this.capacity = capacity
  }

  canReceiveProduct(): boolean {
    return this.queue.length < this.capacity
  }

  receiveReadyProduct(productId: string): void {
    if (this.queue.length < this.capacity) {
      const product = getReadyProduct(productId)
      if (product) {
        product.setFrom(this.id)
        this.onProductReceived(product)
      } else {
        console.log(`[${currentTime}] ❌ ${productId} 没有发现`)
      }
    } else {
      console.log(`[${currentTime}] ❌ ${this.name} 已经满了`)
    }
  }

  onProductReceived(product: Product): void {
    this.queue.push(product)
    messageTransfer('style', null, { targetId: this.id, style: { quantity: this.queue.length } })
    console.log(`[${currentTime}] 📥 ${product.id} 进入 ${this.name}`)

    messageTransfer('product', 'move', { targetId: this.id, productId: product.id })
    this.tryDispatchCurrentProduct()
  }

  // 触发调度但只允许一次调度同时运行
  public tryDispatchCurrentProduct(): void {
    for (const next of this.nextStations) {
      if (this.queue.length > 0 && next.canReceiveProduct(this.id, this.queue[0])) {
        const product = this.queue.shift()
        if (product) {
          addReadyProduct(product)
          next.receiveReadyProduct(product.id)
          messageTransfer('style', null, {
            targetId: this.id,
            style: { quantity: this.queue.length }
          })
        }
      }
    }
  }
}

export default BufferNode

展示效果

相关推荐
程序员海军5 小时前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
前端·后端·程序员
不能放弃治疗5 小时前
MCP
后端
江湖十年6 小时前
Go 语言中 YAML to JSON 踩坑笔记
后端·go·json
你驴我7 小时前
WhatsApp 消息撤回与编辑的幂等性设计实践
java·服务器·前端·后端·python
一缕清烟在人间7 小时前
HarmonyOS开发实战:小分享-TextEditPage文字编辑器——Header+TextArea+工具栏
后端·华为·harmonyos·鸿蒙
人间凡尔赛7 小时前
K8s 十年之变:从 Cloud Native 到 AI Native,2026 年后端架构的范式革命
后端·云原生·架构
程序员cxuan7 小时前
Opus 5 深夜炸场,价格还挺香。。。
人工智能·后端·程序员
北冥you鱼7 小时前
Go 语言新手扫盲:指针 * 和 & 使用场景详解
开发语言·后端·golang
青山木8 小时前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先
卷无止境8 小时前
Python的collections模块:那些被低估的"瑞士军刀"
后端·python