egret+java小游戏服务端09-游戏核心线程池构建、Room接口及抽象类设计


前言

服务端GitHub地址:https://github.com/griii/Hak-Server
小游戏网址:https://www.guorii.cn/hak

线程池构建

使用@Bean将构建出来的线程池放入Spring容器中
单个服务器的线程池,要求:核心100线程,无法添加线程,也就是说等待队列使用不存储的SynchronousQueue即可,拒绝策略为向用户返回拒绝值,或是向其他服务器进行负载均衡

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;

@Configuration
public class MainLogicThreadPool{

    @Value("${MAX_THREAD_THRESHOLD}")
    private int MAX_THREAD_THRESHOLD;

    private static class RejectedHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            //先不做任何处理
        }
    }

    @Bean
    public ExecutorService mainLogicThreadPool(){
        //线程池仅容载每个游戏房间的内容,不是容载每个用户的数量
        //单个服务器的线程池,要求:核心100线程,无法添加线程,也就是说等待队列使用不存储的SynchronousQueue即可,拒绝策略为向用户返回拒绝值
        return new ThreadPoolExecutor(MAX_THREAD_THRESHOLD,
                MAX_THREAD_THRESHOLD,
                60,
                TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(),
                Executors.defaultThreadFactory(),new RejectedHandler());
    }

}


核心线程

核心线程仅需要通过MsgLogic不断地向所有Room内用户广播房间游戏信息,因此主要的内容就是游戏房间。
也就是说有两种方式:
1.MsgLogic内置一个Room作为变量
2.MsgLogic作为一个线程,使用ThreadLocal存储Room

似乎使用起来没有什么区别,所以还是用变量存吧

import com.guorui.hak.entity.room.AbstractRoom;

public class MainGameLogicThread implements Runnable {

    //每个线程拥有一个Room
    private AbstractRoom room;

    @Override
    public void run() {

    }
}

后来又思考了一下,最好还是使用ThreadLocal吧,毕竟线程池核心线程如果设定不关闭,那么Room作为ThreadLocal也是可以重用的,否则每次都需要通过Spring新建一个原型显然不太好。

对于Room为后续扩展维护考虑,使用抽象类实现

//房间信息
@Scope("prototype")
@Component
public abstract class AbstractRoom implements IRoom {

    protected final int INITIAL_CAPACITY = 6;

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : n + 1;
    }

    private ConcurrentHashMap<String, PlayerPeople> players  = new ConcurrentHashMap(tableSizeFor(INITIAL_CAPACITY));

    @Override
    public ConcurrentHashMap<String, PlayerPeople> getUsers() {
        return this.players;
    }

}

设计Room接口的生命周期

由于依赖倒置原则,应当先设定好接口,再以此填充依赖…
Room即游戏房间,可以分为以下几个生命周期
1.加载(游戏使用匹配机制,加载就是匹配房间设定的人数后加载成功,这里可以使用CountDownLatch实现和开始周期的互动。)
2.开始(如上述,当游戏所有人数加载完毕,自动开始游戏内逻辑线程。)
游戏内逻辑仅由msgLogic组成,不断向所有用户广播游戏内信息,而用户的Instruct指令仅由Netty的单线程Handler负责。
3.结束(销毁所有相关线程,生成游戏记录)

以上接口方法由具体实现类实现

想了一下,加载过程不应由Room执行,应当配置匹配策略类,若游戏实行段位机制,维护一个同步队列

最终设计:

1.顶层Room接口

//通用房间接口
public interface IRoom {
    //获取所有用户
    ConcurrentHashMap<String,PlayerPeople> getUsers();
    
    //获取Map容量,适用于初始化
    int getCapacity();
    
    //获取游戏线程是否进行中
    boolean runNow();
      
    //初始化
    void init();
    
    //加载
    void load();
    
    void start();
    
    void finish();
}

二级抽象Room


//房间信息
public abstract class AbstractRoom implements IRoom {

//游戏房间是否正在执行,若正在执行则广播...
    private volatile boolean isRun;

    public boolean runNow(){
        return isRun;
    }

    protected ConcurrentHashMap<String, PlayerPeople> players;

    public void init(){
        this.players = new ConcurrentHashMap<>(MapCapacityUtil.tableSizeFor(getCapacity()));
    }

    public AbstractRoom() {
        init();
    }

    @Override
    public ConcurrentHashMap<String, PlayerPeople> getUsers() {
        return this.players;
    }

}

初步实现的简单3V3房间

@Scope("prototype")
@Component
public class Default3V3Room extends AbstractRoom {

    private static final int CAPACITY = 6;

    @Override
    public int getCapacity() {
        return CAPACITY;
    }

    @Override
    public void load() {

    }

    @Override
    public void start() {

    }

    @Override
    public void finish() {

    }
}

核心线程版本1

@Scope("prototype")
@Component
public class MainGameLogicThread implements Runnable {

    private IRoom room;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    RoomThreadLocal roomThreadLocal;

    public MainGameLogicThread(ThreadLocal<IRoom> threadLocal,String roomType) {
        if (threadLocal.get() == null){
            threadLocal.set((IRoom) applicationContext.getBean(roomType));
        }
    }

    public void reSetUsers(PlayerPeople[] playerPeople) throws RuntimeException{
        if (!room.runNow()){
            throw new RuntimeException("房间线程不在活动,无法修改!");
        }
        try{
            Map<String,PlayerPeople> players = room.getUsers();
            players.clear();//清空
            for (PlayerPeople people:playerPeople){
                players.put(people.getUid()+"",people);
            }
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public void run() throws RuntimeException {
        Map<String,PlayerPeople> players = room.getUsers();
        //如果游戏时间停止,线程终止...
        while(room.runNow()){
            //广播房间信息...
            if (players == null || players.isEmpty()) {
                throw new RuntimeException("游戏房间线程中没有玩家存在!");
            }
            for (Map.Entry<String, PlayerPeople> entry : players.entrySet()) {
                JSONObject jsonObject = JSONObject.fromObject(players);
                TextWebSocketFrameHandler.channelMap.get(entry.getKey() + "").writeAndFlush(new TextWebSocketFrame(jsonObject.toString()));
            }
        }
    }

}

版本1有两个很致命的问题:
1.每个核心线程里都需要注入一个ApplicationContext用来创建Room,显然耦合非常严重
2.以我的思想,这个游戏应当是有匹配机制和创建房间两个机制。

对于匹配机制:
目前思路是创建一个RoomFactory工厂模式,维护一个优先队列用于匹配段位相近的对手

核心线程工厂

详情见注释

@Component
public class MatchRoomFactory implements IRoomFactory {

    //优先队列映射集合
    private Map<Class<? extends IRoom>, PriorityQueue<PlayerPeople>> queueMap = new ConcurrentHashMap<>();

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ExecutorService mainLogicThreadPool;
    
    //用户加入匹配...
    public void join(String roomType,PlayerPeople playerPeople){
        try {
            Class<? extends IRoom> clazz = (Class<? extends IRoom>) Class.forName(roomType);
            if (!queueMap.containsKey(clazz)) {
                //如果不存在,要注意并发安全问题:不能创建多个相同的队列,也就是要做到单例模式
                //由于这个并发问题出现的频率很小啊,所以直接锁就完了
                synchronized (queueMap) {
                    if (queueMap.containsKey(clazz)) {
                        //经典双重检查
                    }
                    queueMap.put(clazz, new PriorityQueue<>());
                }
            }
            Queue queue = queueMap.get(clazz);
            queue.add(playerPeople);
            try{
                //CAPACITY作为实现类的final static字段,直接通过反射拿了
                int capcity = (int) clazz.getDeclaredField("CAPACITY").get(clazz);
                if (queue.size() >= capcity){
                    IRoom room = (IRoom) applicationContext.getBean(roomType);
                    PlayerPeople[] people = new PlayerPeople[capcity];
                    for (int i = 0; i < capcity; i++) {
                        people[i] = (PlayerPeople) queue.element();//用element方法会抛出异常
                    }
                    //调用room的生命周期:load周期,填充PlayerPeople
                    room.load(people);
                    //至此为止,匹配成功了,将这个room添加到核心线程的ThreadLocal中,线程添加到线程池,启动!
                    startThreadRoom(room);
                }
            }catch (Exception e){
                if (e instanceof MyException){
                    //说明是内部再次抛出来的
                    throw e;
                }
                throw new RuntimeException("实现类的CAPACITY字段有误!");
            }
        }catch (Exception e){
            throw new RuntimeException("加入匹配的房间类型格式错误!");
        }
    }


    @Override
    public void startThreadRoom(IRoom room) {
        MainGameLogicThread mainGameLogicThread = (MainGameLogicThread) applicationContext.getBean("mainGameLogicThread");
        mainGameLogicThread.setRoomLocal(room);//设置ThreadLocal
        mainLogicThreadPool.submit(mainGameLogicThread);
    }
}

修改Player实现Comparable

由于匹配机制中是通过优先队列匹配的,因此需要实现Comparable
后续应该会加入段位机制,通过段位来匹配,这里暂时只通过uid来匹配

@Data
public abstract class PlayerPeople implements IPlayer,Comparable<PlayerPeople> {

    public int compareTo(PlayerPeople o){
        return o.uid - this.uid;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java语言游戏项目实战资源包 内容概览: 这次分享为你带来了丰富的Java语言游戏项目实战资源,让你在实践中深入掌握Java语言,并开启游戏开发之旅。资源包中包括: 游戏项目代码:精心挑选了多个经典的小游戏项目,如猜数字、坦克大战等,每个项目都有完整的代码实现。 项目文档:详细的项目文档,介绍了项目的背景、功能、架构以及实现细节,帮助你更好地理解项目。 操作手册与使用说明:针对每个游戏项目,都准备了详细的操作手册和使用说明,手把手教你如何运行和测试项目。 学习笔记:整理了Java语言在游戏开发中的核心知识点和常用技术,方便你随时查阅和学习。 适用人群: 这份资源包适用于所有对Java游戏开发感兴趣的朋友,无论你是计算机专业的学生,还是希望业余时间尝试游戏开发的爱好者,都可以从中受益。 使用建议: 由浅入深实践:建议先从简单的游戏项目开始,逐步深入,既能增强自信,也能逐步提升技术水平。 结合文档与代码:在实践过程中,结合项目文档和代码,理解每一行代码背后的意义和原理。 持续学习与探索:Java语言和游戏开发技术都在不断更新,建议你在实践中持续学习新的技术和工具,不断提升自己。 Java语言游戏项目实战资源包 内容概览: 这次分享为你带来了丰富的Java语言游戏项目实战资源,让你在实践中深入掌握Java语言,并开启游戏开发之旅。资源包中包括: 游戏项目代码:精心挑选了多个经典的小游戏项目,如猜数字、坦克大战等,每个项目都有完整的代码实现。 项目文档:详细的项目文档,介绍了项目的背景、功能、架构以及实现细节,帮助你更好地理解项目。 操作手册与使用说明:针对每个游戏项目,都准备了详细的操作手册和使用说明,手把手教你如何运行和测试项目。 学习笔记:整理了Java语言在游戏开发中的核心知识点和常用技术,方便你随时查阅和学习。 适用人群: 这份资源包适用于所有对Java游戏开发感兴趣的朋友,无论你是计算机专业的学生,还是希望业余时间尝试游戏开发的爱好者,都可以从中受益。 使用建议: 由浅入深实践:建议先从简单的游戏项目开始,逐步深入,既能增强自信,也能逐步提升技术水平。 结合文档与代码:在实践过程中,结合项目文档和代码,理解每一行代码背后的意义和原理。 持续学习与探索:Java语言和游戏开发技术都在不断更新,建议你在实践中持续学习新的技术和工具,不断提升自己。 Java语言游戏项目实战资源包 内容概览: 这次分享为你带来了丰富的Java语言游戏项目实战资源,让你在实践中深入掌握Java语言,并开启游戏开发之旅。资源包中包括: 游戏项目代码:精心挑选了多个经典的小游戏项目,如猜数字、坦克大战等,每个项目都有完整的代码实现。 项目文档:详细的项目文档,介绍了项目的背景、功能、架构以及实现细节,帮助你更好地理解项目。 操作手册与使用说明:针对每个游戏项目,都准备了详细的操作手册和使用说明,手把手教你如何运行和测试项目。 学习笔记:整理了Java语言在游戏开发中的核心知识点和常用技术,方便你随时查阅和学习。 适用人群: 这份资源包适用于所有对Java游戏开发感兴趣的朋友,无论你是计算机专业的学生,还是希望业余时间尝试游戏开发的爱好者,都可以从中受益。 使用建议: 由浅入深实践:建议先从简单的游戏项目开始,逐步深入,既能增强自信,也能逐步提升技术水平。 结合文档与代码:在实践过程中,结合项目文档和代码,理解每一行代码背后的意义和原理。 持续学习与探索:Java语言和游戏开发技术都在不断更新,建议你在实践中持续学习新的技术和工具,不断提升自己。Java语言游戏项目实战资源包 内容概览: 这次分享为你带来了丰富的Java语言游戏项目实战资源,让你在实践中深入掌握Java语言,并开启游戏开发之旅。资源包中包括: 游戏项目代码:精心挑选了多个经典的小游戏项目,如猜数字、坦克大战等,每个项目都有完整的代码实现。 项目文档:详细的项目文档,介绍了项目的背景、功能、架构以及实现细节,帮助你更好地理解项目。 操作手册与使用说明:针对每个游戏项目,都准备了详细的操作手册和使用说明,手把手教你如何运行和测试项目。 学习笔记:整理了Java语言在游戏开发中的核心知识点和常用技术,方便你随时查阅和学习。 适用人群: 这份资源包适用于所有对Java游戏开发感兴趣的朋友,无论你是计算机专业的学生,还是希望业余时间尝试游戏开发的爱好者,都可以从中受益。 使用建议: 由浅入深实践:建议先从简单的游戏项目开始,逐步深入,既能增强自信,也能逐步提升技术水平。 结合文档与代码:在实践过程中,结合项目文档和代码,理解每一行代码背后的意义和原理。 持续学习与探索:Java语言和游戏开发技术都在不断更新,建议你在实践中持续学习新的技术和工具,不

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值