Copy /**
* 进程模块的具体实现
* Process Module Implementation (PMI)
*
* 特点:
* - 在这个实现当中, 同一时刻, 只有一个进程可以运行 (不使用异步函数)
*/
export class PMI implements ProcessModule {
/** 当前正在运行的进程 Id, 用于内部阻塞原子函数识别进程 */
static #currentProcId: number = -1
/** 断言正在运行的进程 Id, 用于检查 */
static #assertCurrentProcId(id: number, task: string) {
if ( !(id in this.#procDict) )
throw `在${task}中, 发现正在运行的进程 Id 为 ${this.#currentProcId}, 找不到应当处于进行态的进程 Id ${id}`
else
assertWithMsg( this.#currentProcId === id, `在${task}中, 发现正在运行的进程 Id 为 ${this.#currentProcId}, 但是进程 ${this.#procDict[id]} 的状态为运行态` )
}
/** 就绪进程 Id 队列 */
static #processIdReadyQueue: number[] = []
/** 阻塞进程 Id 队列 */
static #processIdStuckQueue: number[] = []
/** 映射进程 Id 到进程实体 */
static #procDict: {[ id: number ]: Process} = {}
/** 可用的进程 Id 链表 */
static #idLinkList: number[] = [ ...Array(MAX_PROC_ID).keys() ]
/** 获得下一个进程 Id */
static #getProcId(): number {
// 无可用的进程 Id
if ( PMI.#idLinkList.length === 0 )
throw "Error: 无可用进程 Id";
// 返回进程 Id 链表中第一个可用 Id
return PMI.#idLinkList.shift()
}
/**
* 阻塞进程, 不对外暴露
*/
static #stuckProc(id: number) {
// 找不到进程 id
if ( !(id in this.#procDict) )
throw `Error: 无法找到进程 ${id} 以阻塞`
const proc = this.#procDict[id]
const state = proc.state
if ( state === "Ready" ) {
// 阻塞就绪进程
// 从就绪进程 Id 队列中删去
_.pull(this.#processIdReadyQueue, id)
} else if ( state === "Running" ) {
// 阻塞运行中进程
// 确认当前只有一个进程正在运行
// 并且为正被阻塞的进程
this.#assertCurrentProcId(id, "阻塞")
// 重置正在运行的进程 Id
this.#currentProcId = -1
} else if ( state === "Stuck" ) {
throw `Error: 进程 ${proc} 已经处于阻塞态, 无法再次被阻塞`
}
// 调整状态为阻塞
proc.state = "Stuck"
// 将进程 Id 加入阻塞进程 Id 队列中
this.#processIdStuckQueue.push(id)
}
/**
* 将进程从阻塞态唤醒到就绪态
*
* 注意: 考虑到群惊效应, 进程可能被唤醒多次. 因此, 若
* 进程出于就绪态, 不会报错. 但是因为同一时间只有一个
* 运行的进程, 而自己不能唤醒自己, 会报错.
*/
static #wakeUpProc(id: number) {
// 找不到进程 id
if ( !(id in this.#procDict) )
throw `Error: 无法找到进程 ${id} 以唤醒`
const proc = this.#procDict[id]
const state = proc.state
if ( state === "Ready" ) {
// 已经处于就绪的进程
// 无需处理
} else if ( state === "Stuck" ) {
// 唤醒阻塞中进程
// 从阻塞进程 Id 队列中删除
_.pull(this.#processIdStuckQueue, id)
// 将进程 Id 加入就绪进程 Id 队列中
this.#processIdReadyQueue.push(id)
} else if ( state === "Running" ) {
// 唤醒运行中进程
throw `Error: 运行中的进程 ${proc} 无法再次被唤醒`
}
// 调整状态为就绪
proc.state = "Ready"
}
/**
* 销毁进程, 不对外暴露
* @todo 给未释放的锁, 信号量或管程提出警告
*/
static #destroyProc(id: number) {
// 找不到进程 id
if ( !(id in this.#procDict) )
throw `Error: 无法找到进程 ${id} 以结束`
const proc = this.#procDict[id]
const state = proc.state
if ( state === "Ready" ) {
// 销毁就绪进程
// 从就绪进程 Id 队列中删去
_.pull(this.#processIdReadyQueue, id)
} else if ( state === "Running" ) {
// 销毁运行中进程
// 确认当前只有一个进程正在运行
// 并且为正被销毁的进程
this.#assertCurrentProcId(id, "销毁")
// 重置正在运行的进程 Id
this.#currentProcId = -1
} else if ( state === "Stuck" ) {
// 销毁阻塞进程
// 从阻塞进程 Id 队列中删去
_.pull(this.#processIdStuckQueue, id)
}
// 从映射中删去实体
delete this.#procDict[id]
// 归还进程 Id
this.#idLinkList.push(id)
}
createProc(descriptor: AtomicFuncDescriptor, description: string) {
const id = PMI.#getProcId()
const proc = new Process(id, description, descriptor)
// 将进程注册到映射表中
PMI.#procDict[id] = proc
// 将新创建的进程加入到就绪队列当中
PMI.#processIdReadyQueue.push(id)
return id
}
/** 记录上一次调用 tick 的 Game.time 以保证每 tick 只能执行一次 tick */
static #lastTick: number = -1
tick() {
// 检验为本 tick 第一次调用
assertWithMsg(PMI.#lastTick === -1 || PMI.#lastTick !== Game.time, `进程模块在 ${Game.time} 被重复调用 tick 函数`)
// 校验当前没有正在运行的进程
assertWithMsg(PMI.#currentProcId === -1, `进程模块在 tick 开始时, 发现已有正在运行的进程 Id ${PMI.#currentProcId}`)
// 创建临时就绪进程 Id 队列
const processIdReadyQueue = []
while ( PMI.#processIdReadyQueue.length !== 0 ) {
// 从就绪队列 Id 中出队头 Id
const id = PMI.#processIdReadyQueue.shift()
const proc = PMI.#procDict[id]
// 修改状态
PMI.#currentProcId = id
proc.state = "Running"
// 运行进程
// 这个不作为 Process 的成员函数存在,
// 是因为要访问进程模块的私有成员
while ( true ) {
// 进程执行结束
if (proc.pc >= proc.descriptor.length) {
PMI.#destroyProc(id)
break
}
// 获得当前原子函数描述
const desc = proc.descriptor[proc.pc]
if (Array.isArray(desc) && desc.length === 3) {
// 条件跳转
const condition = desc[1]()
if ( condition )
proc.pc = proc.tagDict[desc[2]]
else
proc.pc++
} else {
// 取得原子函数
let atomicFunc: AtomicFunc = null
if (Array.isArray(desc) && desc.length === 2) atomicFunc = desc[1]
else atomicFunc = desc
const returnCode = atomicFunc()
if (returnCode === OK) {
// 顺序执行下一条原子函数
proc.pc++
} else if (returnCode === "ok_stop_current") {
// 主动停止, 仍然从本条原子函数开始
proc.state = "Ready"
processIdReadyQueue.push(id)
// 复原状态
PMI.#currentProcId = -1
break
} else if (returnCode === "ok_stop_next") {
// 主动停止, 从下一条原子函数开始
proc.pc++
// 特殊情况: 进程结束
if ( proc.pc >= proc.descriptor.length )
PMI.#destroyProc(id)
else {
proc.state = "Ready"
processIdReadyQueue.push(id)
// 复原状态
PMI.#currentProcId = -1
}
break
} else if (returnCode === "stop_stuck") {
// 阻塞, 下次仍然从同一条原子函数开始执行
PMI.#stuckProc(id)
break
} else if (Array.isArray(returnCode) && returnCode[0] === "stop_err") {
// 错误, 资源的释放应当在进程内部完成
// 在下一 tick 重启运行该进程
proc.state = "Ready"
processIdReadyQueue.push(id)
proc.pc = 0
// 输出错误信息
log(LOG_ERR, `运行进程 ${proc} 时, 遇到错误: ${returnCode[1]}`)
// 复原状态
this.#currentProcId = -1
break
} else if (Array.isArray(returnCode) && returnCode[0] === "ok_stop_custom") {
// 复原状态
PMI.#currentProcId = -1
// 主动停止, 从特定 Tag 处开始
const tag = returnCode[1]
if (!(tag in proc.tagDict))
throw `Error: 在执行进程 ${proc} 的过程中, 无法跳转到标签 ${tag}`
proc.state = "Ready"
proc.pc = proc.tagDict[tag]
processIdReadyQueue.push(id)
break
}
}
}
}
// 将进程模块的就绪进程 Id 队列指向临时变量
PMI.#processIdReadyQueue = processIdReadyQueue
// 更新上一次调用函数的时间
PMI.#lastTick = Game.time
}
}