跳到主要内容

FlowContext

介绍

FlowContext 是业务框架 action 流程处理的上下文,生命周期存在于这一次的 action 请求。 如果你之前只接触过 web 开发,可以暂时将其理解为 Cookie。

FlowContext 功能包括但不限于如下:

  • userId 的获取。
  • 具备动态属性能力。
  • 提供通信能力,为各通讯方式提供了同步、异步、异步回调 ...等便捷 api, 并具备全链路调用日志跟踪特性。
  • 支持元信息-附加信息的扩展(开发者可扩展玩家数据的存储)。
  • 支持自定义扩展 (开发者可定制符合团队、符合业务的使用 API)。
  • 得到当前 FlowContext 对应的线程执行器,可轻松实现无锁高并发

由于 FlowContext 具备了通信能力,并且提供了同步、异步、异步回调 ...等便捷 api, 并具备全链路调用日志跟踪特性。 所以你可以使用简洁的代码编写跨服访问,或给自己发送数据。

业务框架流程简图

下图是关于 action 的处理流程 , FlowContext 会经过图中的每个环节,如果开发者想对某个环节做一些特殊的定制扩展的,可以配合上动态属性,几乎可以做任意扩展。

An

如何获取 FlowContext

只需要在 action 的方法参数中定义该变量即可,业务框架会自动识别。

@ActionController(1)
public class DemoAction {
@ActionMethod(1)
public void demo(FlowContext flowContext) {
}
}

userId

玩家登录后,可以通过 flowContext 来获取 userId。

long userId = flowContext.getUserId();

通信能力

FlowContext 内部整合了通讯模型, 并提供了同步、异步、异步回调的便捷使用方式。

更详细的内容,请阅读 javadoc - FlowContext

request/response,请求/响应

示例代码中提供了同步、异步回调、异步的编码风格,其中,异步回调写法具备全链路调用日志跟踪


示例方法说明

  • invokeModuleMessageSync 同步演示
  • invokeModuleMessageAsync 异步回调演示
  • invokeModuleMessageFuture 异步演示
...
void invokeModuleMessageSync() {
// code - 1
ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo);
RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg.roomCount);

// code - 2
ResponseMessage responseMessage2 = flowContext.invokeModuleMessage(cmdInfo, yourData);
RoomNumMsg roomNumMsg2 = responseMessage2.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg2.roomCount);
}

void invokeModuleMessageAsync() {
// code - 1
flowContext.invokeModuleMessageAsync(cmdInfo, responseMessage -> {
RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg.roomCount);
});

// code - 2
flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> {
RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg.roomCount);
});
}

void invokeModuleMessageFuture() {
CompletableFuture<ResponseMessage> future;

try {
// code - 1
future = flowContext.invokeModuleMessageFuture(cmdInfo);
ResponseMessage responseMessage = future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

// code - 2
future = flowContext.invokeModuleMessageFuture(cmdInfo, yourData);
}

request/void,请求/无响应

无阻塞、适合不需要接收响应的业务。 被请求的 action 通常是声明为 void 的,因为知道是无响应的,所以只提供了无阻塞的调用。

...
void invokeModuleVoidMessage() {
// code - 1
flowContext.invokeModuleVoidMessage(cmdInfo);

// code - 2
flowContext.invokeModuleVoidMessage(cmdInfo, yourData);
}

request/multiple_response,请求同类型多个逻辑服

可接收多个游戏逻辑服的响应,示例代码中提供了同步、异步回调、异步的编码风格。 其中,异步回调写法具备全链路调用日志跟踪。


示例方法说明

  • invokeModuleCollectMessageSync 同步演示
  • invokeModuleCollectMessageAsync 异步回调演示
  • invokeModuleCollectMessageFuture 异步演示
void invokeModuleCollectMessageSync() {
// code - 1
ResponseCollectMessage response = flowContext.invokeModuleCollectMessage(cmdInfo);

for (ResponseCollectItemMessage message : response.getMessageList()) {
RoomNumMsg roomNumMsg = message.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg.roomCount);
}

// code - 2
ResponseCollectMessage response2 = flowContext.invokeModuleCollectMessage(cmdInfo, yourData);
log.info("{}", response2.getMessageList());
}

void invokeModuleCollectMessageAsync() {
// code - 1
flowContext.invokeModuleCollectMessageAsync(cmdInfo, responseCollectMessage -> {
List<ResponseCollectItemMessage> messageList = responseCollectMessage.getMessageList();

for (ResponseCollectItemMessage message : messageList) {
RoomNumMsg roomNumMsg = message.getData(RoomNumMsg.class);
log.info("{}", roomNumMsg.roomCount);
}
});

// code - 2
flowContext.invokeModuleCollectMessageAsync(cmdInfo, yourData, responseCollectMessage -> {
log.info("{}", responseCollectMessage.getMessageList());
});
}

void invokeModuleCollectMessageFuture() {
CompletableFuture<ResponseCollectMessage> future;

try {
// code - 2
future = flowContext.invokeModuleCollectMessageFuture(cmdInfo);
ResponseCollectMessage response = future.get();
log.info("{}", response.getMessageList());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

// code - 2
future = flowContext.invokeModuleCollectMessageFuture(cmdInfo, yourData);
}

broadcast,广播

代码说明

  • code 3,全服广播。
  • code 6,广播消息给单个用户。
  • code 11,广播消息给指定用户列表。
  • code 13,给自己发送消息。
  • code 14,给自己发送消息,路由则使用当前 action 的路由。
@ActionMethod(1)
public void broadcast(FlowContext flowContext) {
flowContext.broadcast(cmdInfo, yourData);

long userId = 100;
flowContext.broadcast(cmdInfo, yourData, userId);

List<Long> userIdList = new ArrayList<>();
userIdList.add(100L);
userIdList.add(200L);
flowContext.broadcast(cmdInfo, yourData, userIdList);

flowContext.broadcastMe(cmdInfo, yourData);
flowContext.broadcastMe(yourData);
}
注意

flowContext.broadcastMe 方法需要玩家登录后才能使用。

EventBus,分布式事件总线

分布式事件总线是通讯方式之一,该通讯方式与 Guava EventBus、Redis 发布订阅、MQ ... 等产品类似。

...
void eventBus() {
int userId = 100;
YourEventMessage yourEventMessage = new YourEventMessage(userId);
flowContext.fire(yourEventMessage);
}

访问游戏对外服

只会访问玩家所在的游戏对外服(即使启动了多个游戏对外服)。 示例代码中提供了同步、异步回调、异步的编码风格,其中,异步回调写法具备全链路调用日志跟踪。


示例方法说明

  • invokeExternalModuleCollectMessageSync 同步演示
  • invokeExternalModuleCollectMessageAsync 异步回调演示
  • invokeExternalModuleCollectMessageFuture 异步演示
...
void invokeExternalModuleCollectMessageSync() {
ResponseCollectExternalMessage response;
// code - 1
response = flowContext.invokeExternalModuleCollectMessage(bizCode);
log.info("{}", response);

// code - 2
response = flowContext.invokeExternalModuleCollectMessage(bizCode, yourData);
log.info("{}", response);
}

void invokeExternalModuleCollectMessageAsync() {
// code - 1
flowContext.invokeExternalModuleCollectMessageAsync(bizCode, response -> {
response.optionalAnySuccess().ifPresent(itemMessage -> {
Serializable data = itemMessage.getData();
log.info("{}", data);
});
});

// code - 2
flowContext.invokeExternalModuleCollectMessageAsync(bizCode, yourData, response -> {
});
}

void invokeExternalModuleCollectMessageFuture() {
CompletableFuture<ResponseCollectExternalMessage> future;

try {
// code - 1
future = flowContext.invokeExternalModuleCollectMessageFuture(bizCode);
ResponseCollectExternalMessage response = future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

// code - 2
future = flowContext.invokeExternalModuleCollectMessageFuture(bizCode, yourData);
}

自定义 FlowContext

框架提供了 FlowContext 的扩展,如果 FlowContext 现有的功能无法满足你现有的业务, 可以通过扩展的方式增强 FlowContext。

MyFlowContext 是 FlowContext 子类,我们在 MyFlowContext 中重写了 getAttachment 方法(稍后在元信息小节中会另外做介绍)。

FlowContextFactory 是业务框架 flow 上下文工厂,用于创建自定义的 FlowContext。 在业务框架中,添加自定义的 FlowContextFactory 生产工厂

code 7,设置一个自定义的 FlowContextFactory

@UtilityClass
public class MyBarSkeletonConfig {
public BarSkeleton createBarSkeleton() {
var config = new BarSkeletonBuilderParamConfig();

BarSkeletonBuilder builder = config.createBuilder()
.setFlowContextFactory(MyFlowContext::new);

return builder.build();
}
}

public class MyFlowContext extends FlowContext {
MyAttachment attachment;

@Override
@SuppressWarnings("unchecked")
public MyAttachment getAttachment() {
if (Objects.isNull(attachment)) {
this.attachment = this.getAttachment(MyAttachment.class);
}

return this.attachment;
}

public String getNickname() {
MyAttachment attachment = this.getAttachment();
return attachment.nickname;
}
}

在 action 中的使用

自定义的 MyFlowContext 与框架默认提供的 FlowContext 使用方式是一样的

@ActionController(1)
public class SchoolAction {
@ActionMethod(1)
public String here(MyFlowContext flowContext) {
log.info("{}", flowContext.getClass());
return "MyFlowContext";
}
}

小结 - FlowContext

自定义 FlowContext 是简单的,只需要两个步骤

  1. 自定义 MyFlowContext 类。
  2. 设置 FlowContextFactory 到业务框架中。

元信息-附加信息

FlowContext 对元信息-附加信息更新、获取上提供了便捷的使用方式。

元信息更新 - 1

该示例展示了通过 FlowContext 更新元信息,提供了同步、异步的更新方法。


代码说明

  • code 2,获取元信息。
  • code 5,同步更新,将元信息同步到玩家所在的游戏对外服中。
  • code 6,异步无阻塞更新,将元信息同步到玩家所在的游戏对外服中。
void test(FlowContext flowContext) {
MyAttachment attachment = flowContext.getAttachment(MyAttachment.class);
attachment.nickname = "Michael Jackson";

flowContext.updateAttachment(attachment);
flowContext.updateAttachmentAsync(attachment);
}

public class MyAttachment implements Attachment {
@Getter
long userId;
String nickname;
}

元信息更新 - 2

通过重写 getAttachment 方法,能更加便捷的使用元信息。

代码说明

  • code 2,获取元信息。
  • code 5,同步更新,将元信息同步到玩家所在的游戏对外服中。
  • code 6,异步无阻塞更新,将元信息同步到玩家所在的游戏对外服中。
  • code 12,重写 getAttachment 方法。
void test(MyFlowContext flowContext) {
MyAttachment attachment = flowContext.getAttachment();
attachment.nickname = "渔民小镇";

flowContext.updateAttachment();
flowContext.updateAttachmentAsync();
}

public class MyFlowContext extends FlowContext {
MyAttachment attachment;

@Override
@SuppressWarnings("unchecked")
public MyAttachment getAttachment() {
if (Objects.isNull(attachment)) {
this.attachment = this.getAttachment(MyAttachment.class);
}

return this.attachment;
}
}

动态属性使用

FlowContext 动态的添加属性,这在定制插件或处理特殊业务时很有用。

public final class MyDebugInOut implements ActionMethodInOut {
...
// attr key
final FlowOption<Long> timeKey = FlowOption.valueOf("ExecuteTimeInOutStartTime");

@Override
public void fuckIn(final FlowContext flowContext) {
flowContext.option(timeKey, System.currentTimeMillis());
}

@Override
public void fuckOut(final FlowContext flowContext) {

long currentTimeMillis = System.currentTimeMillis();
Long time = flowContext.option(timeKey);

long ms = currentTimeMillis - time;
log.info("{}", ms);
}
}

线程执行器 - 无锁高并发

FlowContext 提供了获取与玩家所关联线程执行器,包括用户虚拟线程执行器和用户线程执行器。 关于线程相关内容,可阅读 ioGame 线程相关

ioGame 提供了优雅、独特的线程执行器设计。通过该特性,开发者能轻易的编写出无锁高并发的代码。

用户线程执行器

将任务提交到用户所关联的用户线程执行器中执行,该线程执行器也是消费 action 业务的线程执行器。 因为提交的任务与消费玩家 action 业务的是同一个线程执行器,所以可以很好的避免并发问题。


代码说明

  • code 2,使用用户线程执行任务,该方法具备全链路调用日志跟踪。
  • code 6,提交任务到用户线程执行器中。
void executor() {
flowContext.execute(() -> {
// your code
});

flowContext.getExecutor().execute(() -> {
// your code
});
}
注意

不要在该线程执行器中执行耗时的任务,以免阻塞其他玩家。

虚拟线程执行器

将任务提交到用户所关联的用户虚拟线程执行器中执行。


代码说明

  • code 2,使用虚拟线程执行任务,该方法具备全链路调用日志跟踪。
  • code 6,提交任务到虚拟线程执行器中。
void executeVirtual() {
flowContext.executeVirtual(() -> {
// your code
});

flowContext.getVirtualExecutor().execute(() -> {
// your code
});

// for example
flowContext.executeVirtual(() -> {
flowContext.updateAttachment();
});
}

小结

FlowContext 是当前 action 的请求上下文,通过 FlowContext 几乎可以拿到当前逻辑服的所有信息,这些信息包括:逻辑服、业务框架 ... 等。

FlowContext 的动态属性,方便开发者扩展的 option 系列方法(参考 FlowAttr.java)。

框架提供了 FlowContext 的扩展,如果 FlowContext 现有的功能无法满足你现有的业务,可以通过扩展的方式增强 FlowContext。

FlowContext 提供了便捷的通信能力,并提供了同步、异步、异步回调的便捷使用方式。

FlowContext 对元信息的更新、获取上提供了便捷的使用方式。 对于元信息的更新,提供了同步、异步的便捷使用方法,分别是

  • updateAttachment 同步更新
  • updateAttachmentAsync 异步更新

FlowContext 还提供了获取与玩家所关联线程执行器,包括:用户虚拟线程执行器和用户线程执行器。