【在线五子棋对战】十、对战玩家匹配管理模块

文章目录

  • 前言
  • Ⅰ. 匹配队列实现
  • Ⅱ. 匹配队列管理类实现
  • 完整代码

在这里插入图片描述

前言

五子棋对战的玩家匹配是根据自己的天梯分数进行匹配的,而服务器中将玩家天梯分数分为三个档次:

  • 青铜:天梯分数小于 2000
  • 白银:天梯分数介于 2000~3000 分之间
  • 黄金:天梯分数大于 3000

​ 而实现玩家匹配的思想非常简单,为不同的档次设计各自的匹配队列,当一个队列中的玩家数量大于等于 2 的时候,则意味着同一档次中,有两个及以上的人要进行实战匹配,则出队队列中的前两个用户,相当于队首两个个玩家匹配成功,这时候为其创建房间,并将两个用户信息加入房间中。

​ 和之前几个模块设计理念一样,我们要有一个局部模块和全局模块,对于对战玩家匹配模块来说,其实就是将用户放到匹配队列中,等匹配到足够人数的时候,就将匹配人数放到游戏房间里面,所以这里分为两个类:

  • 匹配队列类:就是一个阻塞队列,但其实不一定是用队列实现,具体看下面的讲解
  • 匹配管理类:管理类就是管理多个匹配队列,并且管理用户要进入哪个匹配队列等操作

Ⅰ. 匹配队列实现

​ 匹配队列虽然看起来用队列来实现挺不错的,有先进先出的思想,但是有一个问题,就是玩家可能在匹配的时候,有想取消匹配的操作,那么我们就得提供退出匹配的接口,也就是将用户从匹配队列中删除,但是如果用队列来实现的话,并不是很好办,所以我们采用 双向链表来实现匹配队列

​ 除此之外,因为当队列没有两名成员的时候,是不能进行加入房间操作的,所以我们用 条件变量 + 互斥锁 来实现阻塞队列的功能!所以我们大概要实现的接口如下所示:

  • 数据入队
  • 数据出队
  • 移除指定的数据:注意这和数据出队不太一样,数据出队表示要进入游戏房间了,而 移除指定数据表示取消匹配
  • 获取队列元素个数
  • 阻塞
  • 判断队列为空

​ 因为这些接口实现比较简单,这里直接给出实现,将它们放到 头文件 matcher.hpp 中:

template <class T>
class match_queue
{
private:std::list<T> _block_queue;     // 阻塞队列 -- 用双向链表实现std::mutex _mtx;               // 互斥锁 -- 实现线程安全std::condition_variable _cond; // 条件变量 -- 主要用于阻塞消费者,当队列元素个数小于2的时候阻塞
public:// 获取队列元素个数int size(){std::unique_lock<std::mutex> lock(_mtx);return _block_queue.size();}// 判断队列是否为空bool isEmpty(){std::unique_lock<std::mutex> lock(_mtx);return _block_queue.empty();}// 阻塞线程void wait(){std::unique_lock<std::mutex> lock(_mtx);_cond.wait(lock);}// 数据入队,并唤醒线程void push(T& data){std::unique_lock<std::mutex> lock(_mtx);_block_queue.push_back(data);_cond.notify_all();}// 数据出队 -- 相当于匹配成功要进入房间,data是输出型参数bool pop(T& data){std::unique_lock<std::mutex> lock(_mtx);if(_block_queue.empty())return false;data = _block_queue.front();_block_queue.pop_front();return true;}// 移除指定的数据 -- 相当于取消匹配void remove(T& data){std::unique_lock<std::mutex> lock(_mtx);_block_queue.remove(data);}
};

Ⅱ. 匹配队列管理类实现

​ 因为我们将段位分为了三个段位,为了便于管理,我们 用三个匹配队列来管理三个段位,并且每个匹配队列中还要有各自的线程入口函数,因为如果都放在一个线程中跑的话,此时阻塞力度有点大!下面是管理类的一些成员变量设计:

  • 三个匹配队列对象
  • 三个线程:每个线程分别对应每个匹配队列对象
  • 房间管理类句柄:因为我们要将对应的用户放到对应的房间,那么就得有创建房间等操作
  • 数据库用户信息表句柄:因为我们需要获取用户的天梯分数来判断要将用户放到哪个匹配队列中,所以要有该句柄
  • 在线用户管理句柄:我们需要在用户匹配成功之后,判断一下用户还是否在线,如果不在线了那么就得做一下特殊处理

而管理类的接口无非就是下面三个:

  • 添加用户到匹配队列接口
  • 从匹配队列中移除用户接口
  • 线程入口函数
    • 因为涉及到线程,那么就得有线程入口函数,并且要有三个线程入口函数,由于它们的实现其实是类似的,代码中可以用一个接口来封装一下这三个线程入口函数,减少代码量,具体参考代码
    • 而这三个线程的主要工作无非就是判断各自的匹配队列是否人数大于 2,是的话就要出队两个用户,为他们创建房间,并且向它们发送对战匹配成功的信息,也就是响应,当然匹配失败也要响应!

​ 下面先来看一下匹配对战的 json 数据格式:

开始对战匹配:

{"optype": "match_start"
}
/* 后台正确处理后回复 */
{"optype": "match_start", //表⽰成功加⼊匹配队列"result": true
}
/* 后台处理出错回复 */
{"optype": "match_start""result": false,"reason": "具体原因...."
}
/* 匹配成功了给客户端的回复 */
{"optype": "match_success", //表⽰成匹配成功"result": true
}

停止匹配:

{"optype": "match_stop"
}
/* 后台正确处理后回复 */
{"optype": "match_stop""result": true
}
/* 后台处理出错回复 */
{"optype": "match_stop""result": false,"reason": "具体原因...."
}

​ 所以大体的实现框架如下所示:

class match_manager
{
private:match_queue<uint64_t> _bronze; // 青铜段位队列match_queue<uint64_t> _silver; // 白银段位队列match_queue<uint64_t> _gold;   // 黄金段位队列std::thread _bronze_thread; // 青铜段位线程std::thread _silver_thread; // 白银段位线程std::thread _gold_thread;   // 黄金段位线程online_manager* _onlineptr; // 在线用户管理句柄user_table* _utableptr;     // 数据库用户表信息管理句柄room_manager* _roomptr;     // 房间管理句柄
public:match_manager(online_manager* onlineptr, user_table* utableptr, room_manager* roomptr): _onlineptr(onlineptr), _utableptr(utableptr), _roomptr(roomptr),_bronze_thread(std::thread(&match_manager::_bronze_entry, this)),_silver_thread(std::thread(&match_manager::_silver_entry, this)),_gold_thread(std::thread(&match_manager::_gold_entry, this)){ DLOG("匹配队列管理类初始化完毕...."); }// 添加用户到匹配队列bool addUser(uint64_t uid){}// 将用户从匹配队列中删除,也就是取消匹配bool delUser(uint64_t uid){}private:// 三个段位各自的线程入口函数void _bronze_entry() { return thread_handle(_bronze); }void _silver_entry() { return thread_handle(_silver); }void _gold_entry() { return thread_handle(_gold); }// 总的处理线程入口函数细节的函数// 在这个函数中实现将用户到匹配队列、房间的分配、响应等操作void thread_handle(match_queue<uint64_t>& queue){}
};

​ 💥其中要注意在构造函数中,对于 c++11 方式的线程初始化的时候,指定入口函数前要先指明在哪个类中,并且要取地址,然后将其参数也附上,对于 成员函数来说,默认要传一个 this 指针,不要忘记!

​ 也可以看到,因为三个入口函数其实操作都是一致的,为了避免写大量重复的代码,我们提炼出一个 thread_handle() 函数出来,我们只需要接收一个对应的匹配队列的参数来进行操作即可!

​ 下面我们先来实现添加和删除用户的操作,相对比较简单:

// 根据玩家的天梯分数,来判定玩家档次,添加到不同的匹配队列
bool addUser(uint64_t uid)
{// 1. 根据用户ID,获取玩家信息Json::Value root;bool ret = _utableptr->select_by_id(uid, root);if(ret == false){DLOG("获取玩家:%d 信息失败!!", uid);return false;}uint64_t score = root["score"].asUInt64();// 2. 添加到指定的队列中if(score < 2000)_bronze.push(uid);else if(score >= 2000 && score < 3000)_silver.push(uid);else_gold.push(uid);return true;
}// 将用户从匹配队列中删除,也就是取消匹配
bool delUser(uint64_t uid)
{// 1. 根据用户ID,获取玩家信息Json::Value root;bool ret = _utableptr->select_by_id(uid, root);if(ret == false){DLOG("获取玩家:%d 信息失败!!", uid);return false;}uint64_t score = root["score"].asUInt64();// 2. 将用户从匹配队列中删除if(score < 2000)_bronze.remove(uid);else if(score >= 2000 && score < 3000)_silver.remove(uid);else_gold.remove(uid);return true;
}

​ 可以看到两个函数的操作基本是一致的,其实可以封装一个子接口出来,但是这里就不封装了,它们的区别主要就是添加和删除,其它没有什么问题。

​ 接下来就是最重要的线程入口函数的实现:

// 总的处理线程入口函数细节的函数
// 在这个函数中实现将用户到匹配队列、房间的分配、响应等操作
void thread_handle(match_queue<uint64_t>& queue)
{// 放到死循环中while(1){// 1. 判断队列人数是否大于2,如果小于2则阻塞等待if(queue.size() < 2)queue.wait();// 2. 走到这代表人数够了,出队两个玩家//    这里有细节,如果第一个人出队的时候失败了,那么只需要continue重新开始出队//    但是如果是第二个人出队时候失败了,就要先将已经出队的第一个人的信息重新入队再continueuint64_t uid1;bool ret = queue.pop(uid1);if(ret == false)continue;uint64_t uid2;ret = queue.pop(uid2);if(ret == false){queue.push(uid1); // 要先将出队的那个人重新放到队列中再continuecontinue;}// 3. 校验两个玩家是否在线,如果有人掉线,也就是通信句柄是无效的//    则要把另一个人重新添加入队列,因为当前玩家掉线,而另一个人则需要重新匹配wsserver_t::connection_ptr conn1 =  _onlineptr->get_conn_from_hall(uid1);if(conn1.get() == nullptr){this->addUser(uid2);continue;}wsserver_t::connection_ptr conn2 =  _onlineptr->get_conn_from_hall(uid2);if(conn1.get() == nullptr){this->addUser(uid1);continue;}// 4. 为两个玩家创建房间,并将玩家加入房间中 -- 创建失败的话要重新将用户放到匹配队列room_ptr rp = _roomptr->addRoom(uid1, uid2);if(rp.get() == nullptr){this->addUser(uid1);this->addUser(uid2);continue;}// 5. 对两个玩家进行json数据响应Json::Value response;response["optype"] = "match_success";response["result"] = true;std::string body;json_util::serialize(response, body);conn1->send(body);conn2->send(body);}
}

完整代码

#ifndef __MY_MATCH_H__
#define __MY_MATCH_H__
#include "util.hpp"
#include "online.hpp"
#include "room.hpp"
#include "db.hpp"
#include <mutex>
#include <thread>
#include <condition_variable>
#include <list>template <class T>
class match_queue
{
private:std::list<T> _block_queue;     // 阻塞队列 -- 用双向链表实现std::mutex _mtx;               // 互斥锁 -- 实现线程安全std::condition_variable _cond; // 条件变量 -- 主要用于阻塞消费者,当队列元素个数小于2的时候阻塞
public:// 获取队列元素个数int size(){std::unique_lock<std::mutex> lock(_mtx);return _block_queue.size();}// 判断队列是否为空bool isEmpty(){std::unique_lock<std::mutex> lock(_mtx);return _block_queue.empty();}// 阻塞线程void wait(){std::unique_lock<std::mutex> lock(_mtx);_cond.wait(lock);}// 数据入队,并唤醒线程void push(T& data){std::unique_lock<std::mutex> lock(_mtx);_block_queue.push_back(data);_cond.notify_all();}// 数据出队 -- 相当于匹配成功要进入房间,data是输出型参数bool pop(T& data){std::unique_lock<std::mutex> lock(_mtx);if(_block_queue.empty())return false;data = _block_queue.front();_block_queue.pop_front();return true;}// 移除指定的数据 -- 相当于取消匹配void remove(T& data){std::unique_lock<std::mutex> lock(_mtx);_block_queue.remove(data);}
};class match_manager
{
private:match_queue<uint64_t> _bronze; // 青铜段位队列match_queue<uint64_t> _silver; // 白银段位队列match_queue<uint64_t> _gold;   // 黄金段位队列std::thread _bronze_thread; // 青铜段位线程std::thread _silver_thread; // 白银段位线程std::thread _gold_thread;   // 黄金段位线程online_manager* _onlineptr; // 在线用户管理句柄user_table* _utableptr;     // 数据库用户表信息管理句柄room_manager* _roomptr;     // 房间管理句柄
public:match_manager(online_manager* onlineptr, user_table* utableptr, room_manager* roomptr): _onlineptr(onlineptr), _utableptr(utableptr), _roomptr(roomptr),_bronze_thread(std::thread(&match_manager::_bronze_entry, this)),_silver_thread(std::thread(&match_manager::_silver_entry, this)),_gold_thread(std::thread(&match_manager::_gold_entry, this)){ DLOG("匹配队列管理类初始化完毕...."); }// 根据玩家的天梯分数,来判定玩家档次,添加到不同的匹配队列bool addUser(uint64_t uid){// 1. 根据用户ID,获取玩家信息Json::Value root;bool ret = _utableptr->select_by_id(uid, root);if(ret == false){DLOG("获取玩家:%d 信息失败!!", uid);return false;}uint64_t score = root["score"].asUInt64();// 2. 添加到指定的队列中if(score < 2000)_bronze.push(uid);else if(score >= 2000 && score < 3000)_silver.push(uid);else_gold.push(uid);return true;}// 将用户从匹配队列中删除,也就是取消匹配bool delUser(uint64_t uid){// 1. 根据用户ID,获取玩家信息Json::Value root;bool ret = _utableptr->select_by_id(uid, root);if(ret == false){DLOG("获取玩家:%d 信息失败!!", uid);return false;}uint64_t score = root["score"].asUInt64();// 2. 将用户从匹配队列中删除if(score < 2000)_bronze.remove(uid);else if(score >= 2000 && score < 3000)_silver.remove(uid);else_gold.remove(uid);return true;}private:// 三个段位各自的线程入口函数void _bronze_entry() { return thread_handle(_bronze); }void _silver_entry() { return thread_handle(_silver); }void _gold_entry() { return thread_handle(_gold); }// 总的处理线程入口函数细节的函数// 在这个函数中实现将用户到匹配队列、房间的分配、响应等操作void thread_handle(match_queue<uint64_t>& queue){// 放到死循环中while(1){// 1. 判断队列人数是否大于2,如果小于2则阻塞等待if(queue.size() < 2)queue.wait();// 2. 走到这代表人数够了,出队两个玩家//    这里有细节,如果第一个人出队的时候失败了,那么只需要continue重新开始出队//    但是如果是第二个人出队时候失败了,就要先将已经出队的第一个人的信息重新入队再continueuint64_t uid1;bool ret = queue.pop(uid1);if(ret == false)continue;uint64_t uid2;ret = queue.pop(uid2);if(ret == false){queue.push(uid1); // 要先将出队的那个人重新放到队列中再continuecontinue;}// 3. 校验两个玩家是否在线,如果有人掉线,也就是通信句柄是无效的//    则要把另一个人重新添加入队列,因为当前玩家掉线,而另一个人则需要重新匹配wsserver_t::connection_ptr conn1 =  _onlineptr->get_conn_from_hall(uid1);if(conn1.get() == nullptr){this->addUser(uid2);continue;}wsserver_t::connection_ptr conn2 =  _onlineptr->get_conn_from_hall(uid2);if(conn1.get() == nullptr){this->addUser(uid1);continue;}// 4. 为两个玩家创建房间,并将玩家加入房间中 -- 创建失败的话要重新将用户放到匹配队列room_ptr rp = _roomptr->addRoom(uid1, uid2);if(rp.get() == nullptr){this->addUser(uid1);this->addUser(uid2);continue;}// 5. 对两个玩家进行json数据响应Json::Value response;response["optype"] = "match_success";response["result"] = true;std::string body;json_util::serialize(response, body);conn1->send(body);conn2->send(body);}}
};#endif

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916111.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s之ingress定义https访问方式

接上文&#xff1a;https://blog.csdn.net/soso678/article/details/149607069?spm1001.2014.3001.5502定义后端应用与service [rootmaster ingress]# cat my-nginx.yml apiVersion: apps/v1 kind: Deployment metadata:name: my-nginx spec:selector:matchLabels:run: my-n…

《C++ vector 完全指南:vector的模拟实现》

《C vector 完全指南&#xff1a;vector的模拟实现》 文章目录《C vector 完全指南&#xff1a;vector的模拟实现》一、定义vector的成员变量二、用vector实现动态二维数组三、vector的接口实现1.vector的默认成员函数&#xff08;1&#xff09;构造函数实现&#xff08;2&…

腾讯云代码助手使用指南

腾讯云代码助手使用指南什么是腾讯云代码助手功能区展示功能介绍功能演示一、创建新项目1.先用Chat 把口语化的需求转换成AI更容易接受的结构化提示词2.再用Craft 模式进行代码生成3.成果展示二、老项目探索1.使用Codebase 帮理解项目代码三、代码补全1.只需输入标准的函数名&a…

【vue3+vue-pdf-embed】实现PDF+图片预览

【vue3vue-pdf-embed】实现PDF图片预览项目背景项目代码分析代码项目背景 技术栈&#xff1a;vue3Tselementplus 需要实现PDF和图片预览 图片预览很好解决了&#xff0c;可以用elementplus 自带的组件el-image 可实现 PDF预览可以用搜了一圈&#xff0c;有两个方案&#xff0c…

Leetcode力扣解题记录--第21题(合并链表)

题目链接&#xff1a;21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&…

基于单片机的楼宇门禁系统的设计与实现

2、系统总体设计 2.1硬件的总体设计 为了使门禁系统智能化&#xff0c;需要一个主控芯片对整个门禁系统进行管理控制。接着还需要对应的模块完成包括数字密码验证和IC卡识别验证的功能。当出现非法闯入、验证失败等情况时还需要对操作人员进行警告。最后需要一个人机交互界面方…

全天候自动化数字型智能驱鸟装置,电网防鸟高科技

鸟类在输电线路铁塔、电线杆上筑巢、栖息和排泄是个大问题&#xff0c;很容易引发线路故障导致停电。为了保障电网安全稳定运行&#xff0c;会用到各种智能驱鸟装置来驱赶鸟类&#xff0c;避免涉鸟故障发生。例如全天候自动化数字型智能驱鸟装置&#xff0c;其厉害的地方在于它…

技术、生态与商业:从PC到移动的平台之争

在科技发展的漫长历史中&#xff0c;我们常常惊叹于那些改变世界的伟大技术。然而&#xff0c;深入探究会发现&#xff0c;单纯的技术领先并不能保证最终的胜利。从 PC 操作系统到移动终端&#xff0c;乃至服务器软件&#xff0c;那些最终笑傲江湖的巨头们都遵循着一个共同的法…

android JXL 导出Excel(.xls/xlsx)

前面使用过 POI 导出xlsx但是它体量比较大&#xff0c;功能较丰富&#xff0c;在一些对包size比较敏感并且导出需求相对简单的项目中就不太适合。 poi链接&#xff1a;Android 导入导出excel xls、xlsx_android excel导入导出-CSDN博客 jxl 包体积小&#xff0c;使用简单、AP…

mysql 的主从机制是怎么实现的?

MySQL 作为当前最流行的开源关系型数据库之一&#xff0c;为了满足数据的高可用、负载均衡和容灾备份等需求&#xff0c;广泛应用主从复制&#xff08;Replication&#xff09;机制。其核心思想是&#xff1a;在一台主库&#xff08;Master&#xff09;上发生的所有数据变更都会…

【PHP 函数从入门到精通】

&#x1f9e0; PHP 函数从入门到精通 PHP 函数是编程中最基础、也是最强大的工具之一。它不仅可以简化代码、提高复用性&#xff0c;还能通过各种高级用法&#xff0c;让你写出更灵活、更现代的代码。 下面我们从函数的基础讲起&#xff0c;逐步深入&#xff0c;带你掌握函数的…

CGA老年综合评估汉密尔顿抑郁量表与认知评估联用

一、CGA老年综合评估汉密尔顿抑郁量表与认知评估联用的基础CGA老年综合评估 &#xff08;一&#xff09;二者评估内容的互补性 CGA老年综合评估汉密尔顿抑郁量表主要聚焦于老年人的抑郁情绪及相关症状&#xff0c;而认知评估则着重考察老年人的记忆力、注意力、思维能力等认知…

教培机构如何开发自己的证件照拍照采集小程序

职业教培机构对学员的证件照采集是进行学生培训管理、考试报名、证书发放的前置工作&#xff0c;传统拍照和收集证件照的方式往往面临效率低、质量参差不齐等问题。开发一款专属的证件照拍照采集小程序&#xff0c;不仅能提升机构形象&#xff0c;还能大幅优化工作流程。借助“…

GC8872刷式直流电机驱动器详解:3.6A驱动能力与PWM控制

概述GC8872是一款具有故障报告功能的刷式直流电机驱动芯片&#xff0c;专为打印机、电器、工业设备等机电一体化应用设计。这款芯片采用ESOP8封装&#xff0c;集成了H桥驱动电路和多种保护功能&#xff0c;支持高达3.6A的峰值电流输出。关键特性宽电压工作范围&#xff1a;6.5V…

从0开始学习R语言--Day54--双重固定模型

对于具有空间差异的数据&#xff0c;如果不知道数据的特征关系或意义&#xff0c;直接用杜宾模型来处理是一个比较通用的思路&#xff0c;只是后续还需要很多检验去证明结果的可解释性和统计性。但如果我们已经知道特征的意义&#xff0c;比如企业经济发展的数据中有着员工的科…

三生筛法在计算数论中的极限是什么?

AI辅助创作&#xff1a;三生筛法在计算数论中的极限主要体现在‌规模边界‌、‌算法适应性‌及‌理论兼容性‌三个维度&#xff0c;其核心瓶颈与突破路径如下&#xff1a;一、规模边界&#xff1a;计算效率的断崖式衰减‌‌低维高效区的上限‌在 10^15 以内数域&#xff0c;三生…

iOS WebView 加载失败与缓存刷新问题排查实战指南

在移动 App 中嵌入网页后&#xff0c;不少团队都会遇到一个诡异的问题&#xff1a;用户看到的是“旧内容”&#xff0c;或“资源加载失败”&#xff0c;但在浏览器调试中一切正常。特别是在 iOS WebView 中&#xff0c;这类缓存和加载问题常常隐匿、难以复现。 这篇文章将通过一…

GoLand 项目从 0 到 1:第二天 —— 数据库自动化

第二天核心任务&#xff1a;自动化与多数据库支持第二天的开发聚焦于数据库自动化流程构建与MongoDB 业务链路扩展&#xff0c;通过工具化手段解决数据库操作的重复性问题&#xff0c;同时完善多数据库支持能力。经过一天的开发&#xff0c;项目已实现数据库初始化、迁移、种子…

qt框架,使用webEngine如何调试前端

解决 Qt 5.14.2 中启用开发者工具的问题问题在于 Qt 5.14.2 中 QWebEngineSettings::DeveloperExtrasEnabled 属性已被弃用或更改。正确启用开发者工具的完整方法&#xff08;Qt 5.14.2&#xff09;1. 修改 main.cpp#include <QWebEngineView> #include <QWebEngineSe…

【Atlassian生态】Jira Cloud单站点现可支持10万用户:架构升级与龙智云迁移服务

作为Atlassian全球白金合作伙伴&#xff0c;龙智团队非常激动地宣布&#xff1a;Jira迎来历史性突破——Jira Cloud单个站点最高可支持10万用户&#xff01;覆盖Enterprise、Premium和Standard版本。现在&#xff0c;更多的团队可以将Jira作为核心协作中枢&#xff0c;以加速目…