本帖最后由 redfish 于 2020-6-12 16:47 编辑
之前看到有人说直接注册listener是新手行为,要用runnable注册
但是我看了几个插件,都是直接注册的,
所以,,,,需要用runnable来注册监听器吗
找到看到的地方了
https://www.zhihu.com/question/63200488
之前看到有人说直接注册listener是新手行为,要用runnable注册
但是我看了几个插件,都是直接注册的,
所以,,,,需要用runnable来注册监听器吗
找到看到的地方了
https://www.zhihu.com/question/63200488
本帖最后由 南柯郡守 于 2020-6-12 17:12 编辑
用runnable注册监听器闻所未闻孤陋寡闻
可以 但没必要
可以 但没必要
南柯郡守 发表于 2020-6-12 16:42
用runnable注册监听器闻所未闻
可以 但没必要
找到了,是因为这个playermovevent比较特殊吗
https://www.zhihu.com/question/63200488

本帖最后由 南柯郡守 于 2020-6-12 17:07 编辑
这样啊 我没用过PlayerMoveEvent所以不清楚这个
但是就从API来说 PlayerMoveEvent是理论上触发次数最多的一个事件
而且要求的响应时间极短
所以如果只是简单的处理是不需要用Runnable来注册PlayerMoveEvent这个事件的监听器
(我也不清楚是否用Runnable注册的监听器就是确实异步)
举个简单的例子
比如说我要监听玩家移动并比较玩家是不是在一个区域里面
简单的就是
复制代码
像这样简单的语句 耗时可以忽略不计的语句 是可以直接监听
如果 如果用Runnable注册的监听器就是确实异步的话 那么可以做一些复杂的、耗时长的、可能卡住主线程处理。
但是不能涉及到比如 e.setCancelled(true);
因为你在异步,主线程上可能这个事件已经处理完都走下一步了 你这会来个cancel 主线程要怎么处理?是吧
这个时候服务器就会抛出报错 可能还会崩服
另 在异线程处理之后需要调用一些东西时提示异线程无法操作的 可以使用Bukkit.getScheduler().runTask();来实现
综上 如果Runnable注册的监听器确实异步 那么是可以用来处理复杂的 可能使主线程卡死的一些事件
特别是针对PlayerMoveEvent 这个事件 因为这个事件就算是玩家动一下鼠标都会触发
另 最好不要在PlayerMoveEvent事件中使用e.setCancelled(true); 如有必要 可以使用e.setTo(e.getFrom);
(确实异步指 这个监听器确实是在异线程上运行 而不是 用异线程注册后依然在主线程上 是我瞎编的词 能看懂就好)
(我也是个才学了两个多月的半吊子 对底层的理解不是很深,还请各位大佬指出谬误)
这样啊 我没用过PlayerMoveEvent所以不清楚这个
但是就从API来说 PlayerMoveEvent是理论上触发次数最多的一个事件
而且要求的响应时间极短
所以如果只是简单的处理是不需要用Runnable来注册PlayerMoveEvent这个事件的监听器
(我也不清楚是否用Runnable注册的监听器就是确实异步)
举个简单的例子
比如说我要监听玩家移动并比较玩家是不是在一个区域里面
简单的就是
- if((Vector)(player.getLocation()).isInAABB(min,max)){
- //do something
- }
像这样简单的语句 耗时可以忽略不计的语句 是可以直接监听
如果 如果用Runnable注册的监听器就是确实异步的话 那么可以做一些复杂的、耗时长的、可能卡住主线程处理。
但是不能涉及到比如 e.setCancelled(true);
因为你在异步,主线程上可能这个事件已经处理完都走下一步了 你这会来个cancel 主线程要怎么处理?是吧
这个时候服务器就会抛出报错 可能还会崩服
另 在异线程处理之后需要调用一些东西时提示异线程无法操作的 可以使用Bukkit.getScheduler().runTask();来实现
综上 如果Runnable注册的监听器确实异步 那么是可以用来处理复杂的 可能使主线程卡死的一些事件
特别是针对PlayerMoveEvent 这个事件 因为这个事件就算是玩家动一下鼠标都会触发
另 最好不要在PlayerMoveEvent事件中使用e.setCancelled(true); 如有必要 可以使用e.setTo(e.getFrom);
(确实异步指 这个监听器确实是在异线程上运行 而不是 用异线程注册后依然在主线程上 是我瞎编的词 能看懂就好)
(我也是个才学了两个多月的半吊子 对底层的理解不是很深,还请各位大佬指出谬误)
南柯郡守 发表于 2020-6-12 17:00
这样啊 我没用过PlayerMoveEvent所以不清楚这个
但是就从API来说 PlayerMoveEvent是理论上触发次数最多的 ...
感觉如果让我实现监听器的话,监听线程肯定是异步出来的,难道bukkit不是这样操作吗
redfish 发表于 2020-6-12 17:17
感觉如果让我实现监听器的话,监听线程肯定是异步出来的,难道bukkit不是这样操作吗 ...
就我写了这几个插件的情况来看
不是的 除了AsyncPlayerChatEvent是异线程处理的以外 基本上其他事件都是同步的
即其他事件都是在主线程上同步进行的 必须等你这个处理完了 我才继续往下走
你如果在某个事件监听器中写了一个死循环你就知道了
服务器会卡死 然后spigot会直接停掉服务器
再举个例子
你现在在上课 老师提问你一个数学题 然后等你算答案(主线程等待) 这时候你说 我要拿到边上去算(异步) 老师你先继续讲(主线程忽略你这个监听器的反馈继续执行)。
过了十分钟 你拿着算好的结果来找老师(异线程计算结束反馈) 。
这个时候老师说了 我们都讲完这题下课了你还来干吗
这就好比游戏中比如监听一个爆炸事件
你拿出来异步了
然后主线程已经炸完了 东西破坏了 玩家死了又复活了
你过了一分钟拿着结果让服务器说 你别炸了 伤害也要取消掉
服务器:???
本帖最后由 wisdomme 于 2020-6-12 17:35 编辑
你要是直接监听playermoveevent能给你服务器整跨咯。这个比较特殊,所以需要新建线程来监听。正常触发次数没有这么极其频繁的事件就不需要新建线程来注册事件了。
草?我今天下午刚看到这个,下面评论区还有我呢,哈哈哈哈
我觉得他这个只是举个例子而已
你要是直接监听playermoveevent能给你服务器整跨咯。这个比较特殊,所以需要新建线程来监听。正常触发次数没有这么极其频繁的事件就不需要新建线程来注册事件了。
草?我今天下午刚看到这个,下面评论区还有我呢,哈哈哈哈
我觉得他这个只是举个例子而已
wisdomme 发表于 2020-6-12 17:32
你要是直接监听playermoveevent能给你服务器整跨咯。这个比较特殊,所以需要新建线程来监听。正常触发次数 ...
是的 同时他这个例子也是对的
在一定程度上这些事件都是可以在异线程处理的
只要你不反馈的主线程都是可以异线程处理
比如你只是想要记录一下玩家移动的坐标和路线
那么是可以异线程去监听移动事件 同时也可以简单的把玩家传送一下
其他事件也是 比如我上面举例的爆炸事件
你异步监听如果只是记录一下爆炸地点和时间 也是可以的
本帖最后由 redfish 于 2020-6-12 17:56 编辑
那么问题又来了,如果在新线程里面注册监听器,如果调用主线程的资源是不是会有线程同步问题
原谅我对java多线程了解太少
南柯郡守 发表于 2020-6-12 17:43
是的 同时他这个例子也是对的
在一定程度上这些事件都是可以在异线程处理的
只要你不反馈的主线程都是可 ...
那么问题又来了,如果在新线程里面注册监听器,如果调用主线程的资源是不是会有线程同步问题
原谅我对java多线程了解太少
本帖最后由 wisdomme 于 2020-6-12 18:01 编辑
我记得异步线程中是不可以调用部分主线程方法的
redfish 发表于 2020-6-12 17:53
那么问题又来了,如果在新线程里面注册监听器,如果调用主线程的资源是不是会有线程同步问题 ...
我记得异步线程中是不可以调用部分主线程方法的
wisdomme 发表于 2020-6-12 17:56
我记得异步线程中是不可以调用任何主线程方法的
我自己实现的一个简单功能,可以正常使用啊
- public class ListenerRunnable implements Runnable {
- Plugin plugin;
- public ListenerRunnable(Plugin plugin){
- this.plugin = plugin;
- }
- @Override
- public void run() {
- Bukkit.getServer().getPluginManager().registerEvents(new Listener() {
- @EventHandler
- public void onMMOLevelUp(McMMOPlayerLevelUpEvent event){
- /*
- * todo:某个mcmmo等级达成即可触发命令
- *
- * 挖矿达到等级400
- */
- if(event.getSkill() == MINING && event.getSkillLevel() == 400){
- Bukkit.dispatchCommand(Bukkit.getConsoleSender(), String.format("lp user %s parent add landlord", event.getPlayer().getName()));
- Bukkit.broadcast(String.format("玩家%s挖矿等级达到400,获得领主(可圈地)称号", event.getPlayer().getName()), "mcmmolvup.receive.broadcast");
- }
- }
- }, this.plugin);
- }
- }
redfish 发表于 2020-6-12 17:58
我自己实现的一个简单功能,可以正常使用啊
啊,我记得上次我想搞个啥来着的,但是发现没办法用,最后论坛上问说是不可以在其他线程中调用,忘了是啥了。。。
本帖最后由 redfish 于 2020-6-12 18:08 编辑
是不是用的bukkit给的runnable?
我之前有看到一个文章也是这么说的,但好像是需要用到游戏tick才需要用bukkit提供的,所以我直接用java提供的了
wisdomme 发表于 2020-6-12 18:01
啊,我记得上次我想搞个啥来着的,但是发现没办法用,最后论坛上问说是不可以在其他线程中调用,忘了是啥 ...
是不是用的bukkit给的runnable?
我之前有看到一个文章也是这么说的,但好像是需要用到游戏tick才需要用bukkit提供的,所以我直接用java提供的了
redfish 发表于 2020-6-12 18:07
是不是用的bukkit给的runnable?
我之前有看到一个文章也是这么说的,但好像是需要用到游戏tick才需要用bu ...
哦对!确实如此,但是我因为需要计时所以用的bukkit的runnable
redfish 发表于 2020-6-12 18:07
是不是用的bukkit给的runnable?
我之前有看到一个文章也是这么说的,但好像是需要用到游戏tick才需要用bu ...
比如说你在异线程里面想要给玩家打开一个Inventory是打不开的
会抛出异线程调用主线程方法的错
这个时候可以用
- Bukkit.getScheduler().runTask(plugin,new Runnable(){
- @Override
- public void run(){
- player.openInventory(inv);
- }
- );
这个runTask() 会把你的这个任务排到主线程里的下一个tick 这样就是在主线程运行你的任务了
插件在onEnable中的性能损耗,忽略不计(除非是大型插件)
而且注册几个监听没什么必要用任务调度
可能是指临时事件监听
也就是说比如说就像海螺螺大佬的 completable future 那个教程一样
我举个例子
玩家发送 我是 卢本伟
此时你想要监听玩家发送“卢本伟”这个字符串
玩家发送 我是 pdd
此时你想要监听玩家发送“pdd”这个字符串
那么这个时候你就可以把listener当成是对象
整个事件监听器的类给一个类变量,构造方法传入一个string参数
值就是 卢本伟 或者 pdd 或者是玩家发送的任何东西
再进行事件监听的时候,就可以调用这个变量判断是不是相同的字符串
监听逻辑执行完,取消事件监听(重要!
这就是所谓临时事件监听
一般来说插件主体事件监听直接放在onEnable注册,或者是扫包反射得到newinstance注册(详见PVPINdemoRL的源代码,我写的,只不过没写扫包的教程)
除了临时事件监听,其他的事件监听没必要强行任务调度
而且注册几个监听没什么必要用任务调度
可能是指临时事件监听
也就是说比如说就像海螺螺大佬的 completable future 那个教程一样
我举个例子
玩家发送 我是 卢本伟
此时你想要监听玩家发送“卢本伟”这个字符串
玩家发送 我是 pdd
此时你想要监听玩家发送“pdd”这个字符串
那么这个时候你就可以把listener当成是对象
整个事件监听器的类给一个类变量,构造方法传入一个string参数
值就是 卢本伟 或者 pdd 或者是玩家发送的任何东西
再进行事件监听的时候,就可以调用这个变量判断是不是相同的字符串
监听逻辑执行完,取消事件监听(重要!
这就是所谓临时事件监听
一般来说插件主体事件监听直接放在onEnable注册,或者是扫包反射得到newinstance注册(详见PVPINdemoRL的源代码,我写的,只不过没写扫包的教程)
除了临时事件监听,其他的事件监听没必要强行任务调度
William_Shi 发表于 2020-6-12 21:04
插件在onEnable中的性能损耗,忽略不计(除非是大型插件)
而且注册几个监听没什么必要用任务调度
可能是指 ...
那用新线程来处理会不会确实对性能有提升呢,毕竟主线程就不用花费时间来完成监听器的调用和执行了,对于频繁触发的事件来说是不是效果就比较明显了,比如引用的例子playerMovmentEvent或者BlockBreakEvent这类的。
还有一个就是如果执意要调度来处理的话是不是还要注意公用对象的同步问题。
redfish 发表于 2020-6-12 22:38
那用新线程来处理会不会确实对性能有提升呢,毕竟主线程就不用花费时间来完成监听器的调用和执行了,对于 ...
我的意思是,注册监听没必要搞乱七八糟的操作
执行可以看情况异步
wisdomme 发表于 2020-6-12 17:32
你要是直接监听playermoveevent能给你服务器整跨咯。这个比较特殊,所以需要新建线程来监听。正常触发次数 ...
1. 并不是完全不能用,只要处理的够快,监听PlayerMoveEvent并不会造成性能影响, 这个就看代码优化了。得完全避免进行耗时操作,优化算法复杂度。
2. 而且有些场景必须用PlayerMoveEvent, 因为一个Event可以在一个tick触发多次,要是用Task,你就只能检测到这个tick玩家总共运动了多少,而不是每个运动分别运动了多少。(不过貌似需要这么精确的运动检测只有反作弊需要了)
jebme 发表于 2020-6-21 23:04
1. 并不是完全不能用,只要处理的够快,监听PlayerMoveEvent并不会造成性能影响, 这个就看代码优化了。得 ...
不一定是反作弊
一般来说为了保证性能我监听玩家跳跃都是
先监听玩家统计数据增长
再判断增长的是不是跳跃数据
但是缺点是没有办法进行诸如取消事件那样的操作
(取消统计数据增长,玩家还是跳了)
必须通过玩家移动事件来计算y坐标
然后达到监听跳跃的效果
一般来说都是这样实现二段跳的
监听move事件,性能没你想象的 那么 差
但是的确要避免耗时操作
我写了篇扯 dan 的玩家移动事件高并发

William_Shi 发表于 2020-6-22 07:29
不一定是反作弊
一般来说为了保证性能我监听玩家跳跃都是
先监听玩家统计数据增长
跳跃监听移动Y轴速度就完事了
跳跃高度 = 0.42 + (0.1 * 跳跃药水等级);
顺便判断下是否在地面
跳跃的第1个tick就是这个速度