首页 > 其他分享 >lua开发:有限状态机模式设计

lua开发:有限状态机模式设计

时间:2024-07-20 22:52:07浏览次数:14  
标签:状态 -- 有限 状态机 lua state local event

有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的处理模型。在任意时刻有限状态机都处于某一特定的状态,并且可以根据当前状态和输入条件(触发事件),从当前状态转移到另一个状态。

核心概念:

实体(Entity):状态机的主体和作用对象,它的状态可以改变

状态(State):实体在某一特定时间点的情况,可分为现态(当前状态)、次态(跳转新状态)

事件(Event):导致状态转换的触发器和条件

动作(Action):状态转换时执行的操作

讨论抽象状态对象的实现模式:将对象的状态分离并封装到专用的状态类,使得对象状态可灵活变化,当状态改变时会相应改变对象的行为。

封装状态类

local EventDispatcher = {}

---指定事件输入当前状态对象触发回调动作
---这里回调需要的上下文从外部传入,确保状态对象设计无状态
function EventDispatcher:trigger(context, event, ...)
    local callback = self._event_handler[event]
    local nextState, cmd
    if callback then -- 输入事件符合当前状态对象的期望,触发回调动作
    		-- todo 这里可能需要处理并发
        nextState, cmd = table.unpack(callback)
        local ok = xpcall(context[cmd], debug.traceback, ...)
        return ok, nextState
    end
    return false
end

---创建状态对象
---@param:
---|state:当前状态对象对应的预定义状态
---|events:{ {event_type, next_state, cb_cmd_name}, ... } 当前状态下可接受的事件集
local function createState(state, events)
    local data = {
        _state = state, -- 所在状态
        _event_handler = {} -- 当前状态期望接收的事件及对应触发的动作
    }
    local object = setmetatable(data, { __index = EventDispatcher })
    for _, event in pairs(self._events) do
        self._event_handler[event[1]] = {event[2], event[3]}
    end
    return object
end

状态类维护所有期望的输入事件(条件)与对应触发的动作之间的映射关系,对外提供事件触发处理的API;

状态类设计之上,需要维护一个状态对象集用于描述指定业务的状态切换关系。创建这个集合需要依据业务层预定义的状态关系;

local _config_state_transition -- 业务层预定义的状态对应关系:{ [state] = {event, ...} }

local _state_object = {}, -- 状态to状态对象的映射
for state, events in pairs(_config_state_transition) do
  	_state_object[state] = createState(state, events)
end

封装状态机类:状态机对象与当前所在的状态解耦,将状态类以组合的方式引用到状态机内部,那么,状态机需要有以下属性:

local data = {
    _id = id, -- 关联的外部上下文ID,用来标识代理的业务层对象
    _context = context, -- 引用住外部上下文所在的服务环境,用于状态类回调触发
    _state = state, -- 当前状态机所处的状态
    _state_object = {}, -- 状态to状态对象的映射
  	_event_queue = queue.new(), -- 待处理事件队列
}
local FSM = {}

function FSM:init(...)
  	fork(self.event_dispatch, self) -- 开启事件队列的循环分发处理
end

---@根据当前所在状态获取到对应的状态对象,在对象内触发对应事件的行为
function FSM:trgEvent(event)
  	local stateobj = self._state_object[self._state]
    local ok, nextState = stateobj:trigger(self._context, table.unpack(event))
    if ok and nextState then
        self._state = nextState
    end
end

---@状态机内开启独立的执行序用于循环消费事件队列
function FSM.event_dispatch()
		local event
    while true do
        event = self._event_queue:pop()
        if not event then -- 当前没有待处理事件时挂起,等待队列有新增事件时被唤醒
            self._suspendco = coroutine.running()
            suspend()
            self._suspendco = nil
        else
            self:trgEvent(event)
        end
    end
end

-- 创建状态机实例
local function createEntity(id, context, init_state, ...)
    local data = {
        _id = id,
        _context = context,
        _state = init_state,
        _state_object = _state_object,
        _event_queue = queue.new(),
    		_suspendco = nil,
    }
    local entity = setmetatable(data, { __index = FSM })
    entity:init(...)
    return entity
end

上述设计是针对单状态机业务而言的。

对于一类业务中需要同时维护大量状态变化对象的场景,需要维护批量的状态机实例。上述状态机设计方案在单个实例内维护状态对象的映射以及事件队列,在批量实例的场景下存在消耗叠合的情况。

此时可以抽象出状态机实例管理器,用于统一协调管理批量状态机;

封装Entity管理器:

状态对象集是静态且无状态的,在批量的场景下应该与具体的状态机实例分离,组合在实例管理器中;

实例的事件消费由管理器统一驱动,所有实例的事件触发共用同一事件队列,减少队列重复消耗,使调度更集中;

维护外部上下文ID与状态机实例的映射关系,对业务层隐藏状态机实例设计,业务层通过上下文ID来索引到对应的状态机实例;

外部业务只持有管理器引用;

local data = {
    _modname = modname, -- 所在的上层业务模块名称
    _state_transition = transitionOp, -- 状态预定义表
    _state_object = {}, -- 状态to状态对象的映射
    _id_entity = {}, -- 外部上下文ID to 状态机实例的映射关系
    _event_queue = queue.new(), -- 待处理事件队列
    _suspendco = nil -- dispatcher 空闲挂起时记录coroutine
}
-- 创建当前业务对应的状态对象集
function Manager:init()
    for state, events in pairs(self._state_transition) do
        self._state_object[state] = createState(state, events)
    end

    skynet.fork(self.dispatch, self) -- 开启事件队列的循环分发处理
end

-- 为业务层上下文封装状态机实例
function Manager:createEntity(id, context, state)
    local entity = createEntity(self, id, context, state)
    self._id_entity[id] = entity
end

function Manager:getStateObject(state)
    return self._state_object[state]
end

-- 触发事件,由dispatcher内部使用
local function trgEvent(manager, event)
    local id = table.slice(event, 1, 1)
    local fsm = manager._id_entity[id[1]]
    return fsm:trgEvent(table.unpack(table.slice(event, 2, #event)))
end

function Manager:addEvent(id, ev, ...)
    local event = {id, ev, ...}
    self._event_queue:push(event)
    if self._suspendco then
        skynet.wakeup(self._suspendco)
    end
end

function Manager:dispatcher()
		-- ...
end

local function createManager(modname, transitionOp)
    local data = {
				-- ...
    }
    local manager = setmetatable(data, { __index = Manager })
    manager:init()
    return manager
end

标签:状态,--,有限,状态机,lua,state,local,event
From: https://www.cnblogs.com/linxx-/p/18313921

相关文章

  • lua 游戏架构 之 SceneLoad场景加载(二)
    设计上 定义`NormalSceneLoad`的类,该类继承自`BaseSceneLoad`。lua游戏架构之SceneLoad场景加载(一)-CSDN博客文章浏览阅读48次。设计一个为`BaseSceneLoad`class,用于处理场景加载的相关操作,主要作用是提供了一个通用的场景加载框架https://blog.csdn.net/heyuchang666/a......
  • Evaluating the Factuality of Large Language Models using Large-Scale Knowledge G
    本文是LLM系列文章,针对《EvaluatingtheFactualityofLargeLanguageModelsusingLarge-ScaleKnowledgeGraphs》的翻译。使用大规模知识图谱评估大型语言模型的真实性摘要1引言2相关工作3方法4实验5结论摘要大型语言模型(LLMs)的出现极大地改变了人......
  • ACFI3008 Financial Analysis and Valuation
    ACFI3008FinancialAnalysisandValuationTrimester2,20241. BelowisanarticlepublishedonThe Motley Fool,August7, 10:39amAESTWhyistheResMedsharepricesinkingagainonMonday?ResMedsharesarehavingaverytoughtimethismonth.TheMotl......
  • [十万个为什么] 添加lua交互
    #include"util_lua.h"//调试//---------------------------------------------------------------------------staticinttraceback(lua_State*L){constchar*msg=lua_tostring(L,1);if(msg==NULL){if(luaL_callmeta(L,1,......
  • Auto-GPT Command evaluate_code returned: Error: The model: `gpt-4` does not exis
    题意:Auto-GPT命令evaluate_code返回:错误:模型 gpt-4 不存在。问题背景:I'mworkingwith auto-gpt andIgotthiserror:Commandevaluate_codereturned:Error:Themodel:`gpt-4`doesnotexistandit'slikeitcan'tgofurthermore.whatshouldIdo?......
  • 代码随想录算法训练营第六十六天 | Bellman_ford 队列优化算法(SPFA)、Bellman_ford之
    Bellman_ford队列优化算法(SPFA)题目链接:https://kamacoder.com/problempage.php?pid=1152文档讲解:https://programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8%BF%90%E8%BE%93I-SPFA.html思路Bellman_ford算法每次松弛都是对所......
  • 国产渲染引擎ssRender(Lua+LuaPanda调试篇)
        秉承着说多了都是故事的理念,直接上干货。    今天给大家带来的是一篇关于Lua+Vscode+LuaPanda的远程调试篇,或许对你有一些启发。    资源给大家放在链接里:LuaPanda+LuaSocketDebug资源文件http://[email protected]:hwYang1995/ssRender_Lua_Debug_......
  • 陕西普育思昇教育科技有限公司:18岁少年的自我救赎
    在人生的旅途中,有时候我们会因为各种原因偏离既定的轨道。对于那些18岁就辍学的年轻人来说,未来的道路似乎充满了迷茫和不确定性。然而,只要有追求知识和提升自我的决心,机会总是存在的,而电大中专就是照亮这条求学之路的明灯。18岁,本应是在校园里挥洒青春、汲取知识的年纪,但由......
  • Lua 中的可变长函数
    可变长函数Lua中的可变长函数的参数用...来表示(3个.)在函数内部有一个特殊的内置变量arg其格式如下arg={1,"Hello",true,n=3}--functionmakeVarStr(...)toseeprint_Table.luafunctionprintMultiArg(...) print("...="..makeVarStr(arg))end......
  • 简单理解Lua 协程(coroutine)
    也许更好的阅读体验协程简单理解为可以暂停的线程,但是同一时刻只有一个协程可以处于运行状态。coroutine.create()lua中使用coroutine.create()创建一个协程,参数是一个函数,返回值为创建的协程,这个协程运行内容就是这个函数了。协程有三种状态挂起、运行、停止。协程刚创建时......