文章目录
- 1、为什么要用状态机?
- 2、状态机是什么?
- 3、状态图是什么?
- 4、transitions是什么?
- 官网
- 安装
- 使用状态机必须定义的两个要素
- 二、实战应用
- 1、规划state、transition
- state:状态节点的说明
- transition:状态转移
- 2、编写代码
- 创建一个基础类
- state:状态的定义
- callbacks相关
- 第一种:states的`on_enter`、`on_exit`
- 第二种:给基础类增加动态方法:`on_enter_{stateName}`、`on_exit_{stateName}`
- 第三种:transition的回调`before`属性、`after`属性
- transition:状态转换的定义
- 使用
- 3、代码总结
- 单纯绘图
- 封装写法
- 枚举写法
- auto_transitions参数设置状态机
- 获取转换逻辑,即获取transition
- 批量定义转换规则
- 自反转换 machine.add_transition(dest = "=")
- 内部转换 machine.add_transition(dest=None)
- 顺序转换
- 队列转换
1、为什么要用状态机?
用python做一个比较复杂的小项目,需要根据不同的输入,控制摄像头采集执行不同的任务。虽然用流程方式实现了,但阅读起来费劲,还容易出错。所以就用了状态机。
2、状态机是什么?
有限状态机(Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。
有限状态机是有限个状态以及在这些状态之间的转移和动作等行为的数学模型,例如交通信号灯系统。
3、状态图是什么?
一般可以用状态图来对一个状态机进行精确地描述。
例如这个可乐机的状态图,这里可乐卖50美分 。
4、transitions是什么?
transitions是专门设计好的应用于python的一个有限状态机设计库。
当然状态机的设计可以有很多其他的方法,选用该方法其一是因为更加简单,其二是专业人士写的状态机代码相比于自己写的肯定更加完备一点。
官网
https://github.com/pytransitions/transitions
安装
$ pip3 install transitions
使用状态机必须定义的两个要素
state
:状态节点transition
:状态转移。用于从一个状态节点移动到另一个状态节点
二、实战应用
以物体状态变化为例
1、规划state、transition
state:状态节点的说明
所谓状态机,最先接触的肯定是状态,因此在设计状态机之前需要先明确有哪几种状态。
大自然中物质有4种状态:固态、液态、气态、等离子态(等离子态是热的带电气体)。
所以这里我们规划了物质的4种状态,如下表
条件/当前状态 | 固态 solid | 液态 liquid | 气态 gas | 等离子态 plasma |
---|---|---|---|---|
熔化 melt | 液态 liquid | |||
蒸发 evaporate | 气态 gas | |||
升华 sublimate | 气态 gas | |||
电离 ionize | 等离子态 plasma |
transition:状态转移
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vV8fPnDL-1673197686411)(…/pic/image-20230108094511221.png)]
固态→熔化→液态
液态→蒸发→气态
固态→升华→气态
气态→电离→等离子态
2、编写代码
创建一个继承
object
的类Matter
的实体对象lump
,然后调用transitions.Machine()
将状态机绑定到这个实体对象上。最终得到了两个东西,一个是状态机对象
machine
,一个是具体的实体对象lump
。之后设定状态机是用
machine
,运行状态机是用具体的实体对象lump
。
创建一个基础类
先定义一个类,把它当成基础模型,Matter就是物质类,lump不好理解的话就理解成’冰’。
## 1、创建基础类
class Matter:
pass
lump = Matter()
state:状态的定义
支持两种定义方式
方式一:状态可以是列表的形式,列表里的形式可以是类、字符串或字典:
from transitions import Machine, State
## 1、创建基础类
class Matter:
pass
lump = Matter()
## 2、定义状态
states = [
State(name='solid'), # 类,<State('solid')@4472798992>
'liquid', # 字符串
{'name': 'gas'}, # 字典
"plasma"
]
## 3、将状态加入到状态机,并将状态机绑定到lump实例对象上
machine = Machine(lump, states) # <transitions.core.Machine object at 0x10bb1a8b0>
方式二:也可以应用到transitions模块提供的State类进行初始化,再用add_states()
进行添加:
from transitions import Machine, State
## 1、创建基础类
class Matter:
pass
lump = Matter()
## 2、绑定
machine = Machine(lump)
## 3、定义状态
solid = State('solid')
liquid = State('liquid')
gas = State('gas')
plasma = State('plasma')
## 4、将状态加入到状态机上
machine.add_states([solid, liquid, gas]) # <transitions.core.Machine object at 0x10ac6df10>
callbacks相关
有三种方式:
- 使用states的
on_enter
、on_exit
属性进行设置。
on_enter
:进入该状态后做的事,此时状态已经转换了。on_exit
:即将离开某状态时做的事,此时状态未转换。- 给基础类增加动态方法:
on_enter_{stateName}
、on_exit_{stateName}
- transition的
before
属性、after
属性
before
: 在 属性转换方法 调用即将开始前进行回调,此时属性未转变。after
: 在 属性转换方法 调用即将结束前进行回调,此时属性已改变。先后顺序(使用转换条件进行状态转换,即执行
lump.melt()
,实现由solid -> liquid):
- transition的回调
before
属性- state=solid的回调
on_exit
属性- 基础类动态方法的回调
on_exit_solid()
方法- 【此时状态已改变】
- state=liquid的回调
on_enter
属性- 基础类动态方法的回调
on_enter_liquid()
方法- transition的回调
after
属性
第一种:states的on_enter
、on_exit
可以理解为“回调”或者“钩子”。
on_enter
:进入该状态后做的事(会执行Matter类中相应名字的函数),注意此时状态已经转换了。on_exit
:即将离开某状态时做的事(会执行Matter类中相应名字的函数),注意此时状态未转换。
from transitions import Machine, State
## 1、创建基础类
class Matter(object):
def say_hello(self):
print("hello, new state!")
def say_goodbye(self):
print("goodbye, old state!")
lump = Matter()
## 2、定义状态,同时给某些状态设置回调
states = [
State(name='solid', on_exit=['say_goodbye']),
'liquid',
{'name': 'gas', 'on_exit': ['say_goodbye']},
{'name': 'plasma', 'on_enter': ['say_hello']}
]
## 3、绑定并将状态加入到状态机上
machine = Machine(lump, states=states)
## 4、追加设置某些状态的回调
machine.on_enter_liquid('say_hello') # 使用'on_enter_{stateName},动态方法'的方式设置进入liquid状态时执行的回调
第二种:给基础类增加动态方法:on_enter_{stateName}
、on_exit_{stateName}
# 代码看不懂可以先往下看,回头再来看即可
from transitions import State, Machine
class Matter:
def say_hello(self):
print('hello, ' + self.state + ' state!')
def say_goodbye(self):
print('goodbye, ' + self.state + ' state!')
def on_exit_solid(self):
print('Current ' + self.state + ' state!')
def on_enter_liquid(self):
print('Current ' + self.state + ' state!')
def __init__(self):
states = [
State(name='solid', on_enter='say_hello', on_exit='say_goodbye'),
State(name='liquid', on_enter='say_hello', on_exit='say_goodbye'),
State(name='gas', on_enter='say_hello', on_exit='say_goodbye'),
State(name='plasma', on_enter='say_hello', on_exit='say_goodbye'),
]
self.machine = Machine(model=self, states=states, initial=states[0])
self.machine.add_transition(trigger='melt', source='solid', dest='liquid')
self.machine.add_transition(trigger='evaporate', source='liquid', dest='gas')
self.machine.add_transition(trigger='sublimate', source='solid', dest='gas')
self.machine.add_transition(trigger='ionize', source='gas', dest='plasma')
lump = Matter()
print(lump.state) # solid
lump.melt()
# goodbye, solid state!
# Current solid state!
# hello, liquid state!
# Current liquid state!
print(lump.state) # liquid
print(lump.is_solid()) # Fasle
print(lump.is_liquid()) # True
第三种:transition的回调before
属性、after
属性
from transitions import State, Machine
class Matter:
def say_hello(self):
print('hello, ' + self.state + ' state!')
def say_goodbye(self):
print('goodbye, ' + self.state + ' state!')
def on_exit_solid(self):
print('exit Current ' + self.state + ' state!')
def on_enter_liquid(self):
print('enter Current ' + self.state + ' state!')
def hello(self):
print('hello after, ' + self.state + ' state!')
def goodbye(self):
print('goodbye before, ' + self.state + ' state!')
def __init__(self):
states = [
State(name='solid', on_enter='say_hello', on_exit='say_goodbye'),
State(name='liquid', on_enter='say_hello', on_exit='say_goodbye'),
State(name='gas', on_enter='say_hello', on_exit='say_goodbye'),
State(name='plasma', on_enter='say_hello', on_exit='say_goodbye'),
]
self.machine = Machine(model=self, states=states, initial=states[0])
# before: 在 属性转换方法 调用开始之前进行回调,此时属性未转变
# after: 在 属性转换方法 调用即将结束前进行回调,此时属性已改变
self.machine.add_transition(trigger='melt', source='solid', dest='liquid', before='goodbye', after='hello')
self.machine.add_transition(trigger='evaporate', source='liquid', dest='gas')
self.machine.add_transition(trigger='sublimate', source='solid', dest='gas')
self.machine.add_transition(trigger='ionize', source='gas', dest='plasma')
lump = Matter()
print(lump.state) # solid
lump.melt()
# goodbye before, solid state! # transition的回调`before`属性
# goodbye, solid state! # state=solid的回调`on_exit`属性
# exit Current solid state! # 基础类动态方法的回调`on_exit_{stateName}`方法
# hello, liquid state! # state=liquid的回调`on_enter`属性
# enter Current liquid state! # 基础类动态方法的回调`on_enter_{stateName}`方法
# hello after, liquid state! # transition的回调`after`属性
print(lump.state) # liquid
print(lump.is_solid()) # Fasle
print(lump.is_liquid()) # True
transition:状态转换的定义
支持两种定义方式
方式一:列表的形式,状态切换的格式如下所示:
from transitions import Machine, State
## 1、创建基础类
class Matter(object):
def say_hello(self):
print("hello, new state!")
def say_goodbye(self):
print("goodbye, old state!")
lump = Matter()
## 2、定义状态,同时给某些状态设置回调
states = [
State(name='solid', on_exit=['say_goodbye']),
'liquid',
{'name': 'gas', 'on_exit': ['say_goodbye']},
State(name='plasma', on_enter=['say_hello'])
]
## 3、定义转换规则
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' },
{ 'trigger': 'any', 'source': '*', 'dest': 'solid' } # *为任意状态
]
## 4、绑定并将状态及转换规则加入到状态机上
machine = Machine(model=lump, states=states, transitions=transitions)
定义转换规则可以简化为:
transitions = [
['melt', 'solid', 'liquid'],
['evaporate', 'liquid', 'gas'],
['sublimate', 'solid', 'gas'],
['ionize', 'gas', 'plasma'],
['any', '*', 'solid']
]
方式二:通过add_transition
函数添加转换:
from transitions import Machine, State
## 1、创建基础类
class Matter(object):
def say_hello(self):
print("hello, new state!")
def say_goodbye(self):
print("goodbye, old state!")
lump = Matter()
## 2、定义状态,同时给某些状态设置回调
states = [
State(name='solid', on_exit=['say_goodbye']),
'liquid',
{'name': 'gas', 'on_exit': ['say_goodbye']},
State(name='plasma', on_enter=['say_hello'])
]
# 3、绑定并将状态加入到状态机上,同时指定初始状态为'solid'
# 如果未指定初始状态则默认为'initial'
machine = Machine(model=lump, states=states, initial='solid')
# 4、添加转换条件
machine.add_transition('melt', source='solid', dest='liquid')
# machine.add_transition('melt', source='solid', dest='plasma') # 只有第一个匹配melt的transition有效,因为它们trigger相同,source也相同,所以即使写了此行也不会生效。
# machine.add_transition('melt', source='gas', dest='plasma') # 相同trigger时source不同的择机匹配,即melt()的source支持solid或gas,如果source都不是则抛出异常
machine.add_transition('evaporate', source='liquid', dest='gas')
machine.add_transition('sublimate', source='solid', dest='gas')
machine.add_transition('ionize', source='gas', dest='plasma')
使用
## 把 完成"状态转换的定义"后的任意代码放到此处即可
# 打印当前状态
print(lump.state)
# is_{stateName}判断当前状态是否为solid
print(lump.is_solid()) # 返回布尔值
# (非常不推荐使用,会无视设置的转换条件)强制转换到liquid状态
lump.to_liquid()
# (推荐)使用转换条件进行状态转换,如不符合设置的转换条件则会抛出异常
lump.melt()
# (也可)使用trigger方法调用melt转化,如不符合设置的转换条件则会抛出异常
lump.trigger("melt")
# 获取solid状态触发器
machine.get_triggers('solid')
3、代码总结
单纯绘图
主要用来前期理清 state、transition思路
from transitions.extensions import GraphMachine
"""
GraphMachine依赖graphviz,需要先安装`pip3 install graphviz`
并确保Graphviz可执行文件在您的系统上PATH
https://github.com/xflr6/graphviz
http://graphviz.org/
"""
class Matter:
pass
states = ['固态 solid', '液态 liquid', '气态 gas', '等离子态 plasma']
transitions = [
{'trigger': '熔化 melt', 'source': '固态 solid', 'dest': '液态 liquid'},
{'trigger': '蒸发 evaporate', 'source': '液态 liquid', 'dest': '气态 gas'},
{'trigger': '升华 sublimate', 'source': '固态 solid', 'dest': '气态 gas'},
{'trigger': '电离 ionize', 'source': '气态 gas', 'dest': '等离子态 plasma'}
]
machine = GraphMachine(Matter(), states=states, transitions=transitions, initial='固态 solid')
# machine = GraphMachine(lump, states=states, transitions=transitions, initial='solid', show_auto_transitions=True) # 完整流程
graph = machine.get_graph()
graph.edge_attr['fontname'] = 'Microsoft Yahei'
graph.node_attr['fontname'] = 'Microsoft Yahei'
graph.graph_attr['fontname'] = 'Microsoft Yahei'
graph.graph_attr['dpi'] = '300' # 设置分辨率
graph.graph_attr.pop('label') # 删除标题
graph.draw('result.png', prog='dot')
封装写法
from transitions import Machine
class Matter:
states = ['solid', 'liquid', 'gas', 'plasma'] # 状态有固态、液态、气态、等离子态
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
def __init__(self):
"""不定型物体
固态→熔化→液态
液态→蒸发→气态
固态→升华→气态
气态→电离→等离子态
"""
self.machine = Machine(model=self, states=Matter.states, transitions=Matter.transitions, initial='solid')
lump = Matter()
print(lump.state) # solid
lump.melt()
print(lump.state) # liquid
枚举写法
from enum import Enum
from transitions import Machine
class States(Enum):
ERROR = 0
RED = 1
YELLOW = 2
GREEN = 3
transitions = [
# 注意这里有两个名为proceed的trigger,代表着匹配到哪个source就用哪个,如果都没匹配到则抛出异常
['proceed', States.RED, States.YELLOW],
['proceed', States.YELLOW, States.GREEN],
['error', '*', States.ERROR]
]
m = Machine(states=States, transitions=transitions, initial=States.RED)
assert m.is_RED(), "当前状态不是红色"
assert m.state is States.RED, "当前状态不是红色"
state = m.get_state(States.RED) # <State('RED')@4436112768>
print(state.name) # RED
m.proceed() # RED -> YELLOW
m.proceed() # YELLOW -> GREEN
assert m.is_GREEN(), "当前状态不是绿色"
m.error()
assert m.state is States.ERROR
auto_transitions参数设置状态机
当 True(默认值)时,每个状态都会在基本模型中自动具有关联的 to_{state}() 便利触发器。
from transitions import Machine
class Matter:
pass
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
lump = Matter()
machine = Machine(model=lump, states=states, transitions=transitions, initial='solid')
print(lump.state) # solid
print(machine.get_triggers('solid')) # 获取固态的触发器(转换状态的函数)
lump.to_plasma() # 强制转换为等离子态 (不推荐使用强制转换,这里仅测试)
print(lump.state) # plasma
print()
lump = Matter()
machine = Machine(model=lump, states=states, transitions=transitions, initial='solid', auto_transitions=False) # 禁止自动转换
print(lump.state) # 初始化状态为固态solid
print(machine.get_triggers('solid')) # 获取固态的触发器(转换状态的函数), ['melt', 'sublimate']
try:
lump.to_plasma() # 尝试转换为等离子态,失败
except Exception as e:
print(e) # 'Matter' object has no attribute 'to_plasma'
lump.melt()
print(lump.state) # liquid
获取转换逻辑,即获取transition
from transitions import Machine
class Matter:
pass
def get_transitions(machine, auto_transitions=False):
"""获取转换逻辑"""
transitions = []
for trigger, event in machine.events.items():
if auto_transitions is False and trigger.startswith('to_'):
continue
for source, _transitions in event.transitions.items():
for i in _transitions:
transitions.append({'trigger': trigger, 'source': source, 'dest': i.dest})
return transitions
lump = Matter()
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
machine = Machine(model=lump, states=states, transitions=transitions)
print(get_transitions(machine))
"""
[
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
"""
批量定义转换规则
add_transition()的参数
source
可为列表或字符串或通配符*
from transitions import Machine
class Matter:
pass
lump = Matter()
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
machine = Machine(model=lump, states=states, transitions=transitions, auto_transitions=False) # 禁止自动转换
machine.add_transition('transmogrify', ['solid', 'liquid', 'gas'], 'plasma') # 批量添加不同source
machine.add_transition('to_liquid', '*', 'liquid') # 通配符添加
print(machine.get_triggers('solid'))
print(machine.get_triggers('liquid'))
print(machine.get_triggers('gas'))
print(machine.get_triggers('plasma'))
# ['melt', 'sublimate', 'transmogrify', 'to_liquid']
# ['evaporate', 'transmogrify', 'to_liquid']
# ['ionize', 'transmogrify', 'to_liquid']
# ['to_liquid']
自反转换 machine.add_transition(dest = “=”)
自己转换为自己,注意状态转换是实际发生的,所以会调用state相关的回调
on_exit
或on_enter
。
from transitions import Machine
class Matter:
def say_hello(self):
print("hello, new state!")
def say_goodbye(self):
print("goodbye, old state!")
def change_shape():
print(1)
lump = Matter()
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
machine = Machine(model=lump, states=states, transitions=transitions, initial='solid', auto_transitions=False) # 禁止自动转换
machine.add_transition('touch', ['liquid', 'gas', 'plasma'], '=', after=change_shape) # 自反转换,并在转换后调用某函数
machine.on_enter_liquid('say_hello')
print(machine.get_triggers('solid'))
print(machine.get_triggers('liquid'))
print(machine.get_triggers('gas'))
print(machine.get_triggers('plasma'))
print()
# ['melt', 'sublimate']
# ['evaporate', 'touch']
# ['ionize', 'touch']
# ['touch']
print(lump.state)
lump.melt()
print(lump.state)
lump.touch()
print(lump.state)
# solid
# hello, new state!
# liquid
# hello, new state!
# 1
# liquid
内部转换 machine.add_transition(dest=None)
不同于自反转换,内部转换不会真正转换状态,意味着transition的回调before
或after
会被调用,而state相关的回调on_exit
或on_enter
则不会被调用。
from transitions import Machine
class Matter:
def say_hello(self):
print("hello, new state!")
def say_goodbye(self):
print("goodbye, old state!")
def change_shape():
print(1)
lump = Matter()
states = [
{'name': 'solid'},
{'name': 'liquid', "on_exit": ['say_goodbye']},
'gas',
'plasma'
]
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
machine = Machine(model=lump, states=states, transitions=transitions, initial='solid', auto_transitions=False) # 禁止自动转换
machine.add_transition('internal', ['liquid', 'gas'], None, after=change_shape) # 自反转换,并在转换后调用某函数
machine.on_enter_liquid('say_hello')
print(machine.get_triggers('solid'))
print(machine.get_triggers('liquid'))
print(machine.get_triggers('gas'))
print(machine.get_triggers('plasma'))
print()
# ['melt', 'sublimate']
# ['evaporate', 'internal']
# ['ionize', 'internal']
# []
print(lump.state)
lump.melt()
print(lump.state)
lump.internal() # 内部转换,不会真正转换状态,意味着transition的回调如before或after会被调用,而state相关的回调如on_exit或on_enter则不会被调用
print(lump.state)
# solid
# hello, new state!
# liquid
# 1
# liquid
顺序转换
常见的需求是转换状态按顺序转换,如给定状态 ['A', 'B', 'C']
,转换状态的情况只有 A → B
、 B → C
、 C → A
。
调用 add_ordered_transitions()
import random
from transitions import Machine
class Matter:
pass
def check():
success = random.randint(0, 1) # 生成一个指定范围内的整数,包含端点
if success:
print('转换状态')
else:
print('不转换状态')
return success
def f1():
return check()
def f2():
return check()
def f3():
return check()
states = ['A', 'B', 'C']
machine = Machine(states=states, initial=states[0])
machine.add_ordered_transitions()
# machine.add_ordered_transitions(['A', 'C', 'B']) # 指定顺序
# machine.add_ordered_transitions(conditions=check) # 为进行转换必须通过的条件,True则转换状态
# machine.add_ordered_transitions(conditions=[f1, f2, f3]) # 同上,列表里的个数与states同,一一对应
# machine.add_ordered_transitions(loop=False) # 禁止循环
for _ in range(4): # 转换四次
print(machine.state)
machine.next_state()
# A
# B
# C
# A
队列转换
队列特点是先进先出。
转换状态会马上处理事件,意味着 on_enter
会在 after
前调用。