工业仿真(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

展示效果

相关推荐
SamDeepThinking3 小时前
使用Cursor生成【财务对账系统】前后端代码
后端·ai编程·cursor
饭碗的彼岸one3 小时前
C++ 并发编程:异步任务
c语言·开发语言·c++·后端·c·异步
会豪3 小时前
工业仿真(simulation)--仿真引擎,离散事件仿真(1)
后端
Java微观世界3 小时前
匿名内部类和 Lambda 表达式为何要求外部变量是 final 或等效 final?原理与解决方案
java·后端
SimonKing3 小时前
全面解决中文乱码问题:从诊断到根治
java·后端·程序员
几颗流星4 小时前
Java 中使用 CountDownLatch 增加线程竞争,帮助复现并发问题
后端
郑洁文4 小时前
基于SpringBoot的天气预报系统的设计与实现
java·spring boot·后端·毕设
optimistic_chen4 小时前
【Java EE进阶 --- SpringBoot】Spring DI详解
spring boot·笔记·后端·spring·java-ee·mvc·di
Java水解4 小时前
【MySQL】数据库基础
后端·mysql