From fe3e581f8685515530e7fd45f47a717077ac7189 Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Mon, 14 Nov 2022 10:25:28 +0100 Subject: [PATCH] Make client callbacks arrays Allows to setup multiple on_* hook blocks as a lib user closed #10 --- README.md | 2 +- client_sample.rb | 2 +- docs/v0.0.1/classes/TeeworldsClient.md | 8 ++- examples/01_chat_logger.rb | 2 +- examples/05_chatbot.rb | 2 +- integration_test/client/multiple_blocks.rb | 24 +++++++ integration_test/run.sh | 34 +++++++++- lib/context.rb | 29 ++++++++ lib/game_client.rb | 79 ++++++++-------------- lib/teeworlds_client.rb | 27 +++++--- scripts/hooks.sh | 4 +- 11 files changed, 141 insertions(+), 72 deletions(-) create mode 100755 integration_test/client/multiple_blocks.rb create mode 100644 lib/context.rb diff --git a/README.md b/README.md index d30563d..8765189 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ require_relative 'lib/teeworlds_client' client = TeeworldsClient.new(verbose: false) -client.on_chat do |msg| +client.on_chat do |_, msg| # note use `next` instead of `return` in the block next unless msg.message[0] == '!' diff --git a/client_sample.rb b/client_sample.rb index fd50b11..a950d70 100755 --- a/client_sample.rb +++ b/client_sample.rb @@ -28,7 +28,7 @@ args[:port] = args[:port] || 8303 client = TeeworldsClient.new(verbose: args[:verbose]) -client.on_chat do |msg| +client.on_chat do |_, msg| puts "[chat] #{msg}" end diff --git a/docs/v0.0.1/classes/TeeworldsClient.md b/docs/v0.0.1/classes/TeeworldsClient.md index 01c7ffc..ae8fb15 100644 --- a/docs/v0.0.1/classes/TeeworldsClient.md +++ b/docs/v0.0.1/classes/TeeworldsClient.md @@ -87,17 +87,19 @@ client.connect('localhost', 8303, detach: true) ### #on_chat(&block) -**Parameter: block [Block |[ChatMessage](../classes/ChatMessage.md)|]** +**Parameter: block [Block |[context](../classes/Context.md), [ChatMessage](../classes/ChatMessage.md)|]** Takes a block that will be called when the client receives a chat message. -The block takes one parameter of type [ChatMessage](../classes/ChatMessage.md). +The block takes two parameters: + [context](../classes/Context.md) - pretty much useless as of right now + [ChatMessage](../classes/ChatMessage.md) - holds all the information of the chat message **Example:** ```ruby client = TeeworldsClient.new -client.on_chat do |msg| +client.on_chat do |context, msg| puts "[chat] #{msg}" end diff --git a/examples/01_chat_logger.rb b/examples/01_chat_logger.rb index 983a2f2..8498800 100755 --- a/examples/01_chat_logger.rb +++ b/examples/01_chat_logger.rb @@ -18,7 +18,7 @@ client = TeeworldsClient.new(verbose: false) # msg.author.team # msg.author.name # msg.author.clan -client.on_chat do |msg| +client.on_chat do |_, msg| puts "[chat] #{msg}" end diff --git a/examples/05_chatbot.rb b/examples/05_chatbot.rb index 69ecec8..82b8d86 100755 --- a/examples/05_chatbot.rb +++ b/examples/05_chatbot.rb @@ -11,7 +11,7 @@ require_relative '../lib/teeworlds_client' client = TeeworldsClient.new(verbose: false) -client.on_chat do |msg| +client.on_chat do |_, msg| next if msg.message[0] != '!' case msg.message[1..] diff --git a/integration_test/client/multiple_blocks.rb b/integration_test/client/multiple_blocks.rb new file mode 100755 index 0000000..9f2a8e7 --- /dev/null +++ b/integration_test/client/multiple_blocks.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../../lib/teeworlds_client' + +client = TeeworldsClient.new + +client.on_connected do |_| + puts 'block 1 rcon auth' + client.rcon_auth(password: 'rcon') +end + +client.on_connected do |_| + puts 'block 2 rcon shutdown' + client.rcon('shutdown') +end + +client.on_disconnect do + puts 'got disconnect' + exit 0 +end + +# connect and block main thread +client.connect('localhost', 8377, detach: false) diff --git a/integration_test/run.sh b/integration_test/run.sh index c91ae08..4886cf0 100755 --- a/integration_test/run.sh +++ b/integration_test/run.sh @@ -78,9 +78,9 @@ then fi if [[ "$testname" =~ ^client/ ]] then - ruby_logfile="$logdir/ruby_client.tx" + ruby_logfile="$logdir/ruby_client.txt" else - ruby_logfile="$logdir/ruby_server.tx" + ruby_logfile="$logdir/ruby_server.txt" fi function cleanup() { @@ -160,6 +160,36 @@ then then fail "Error: server still running rcon shutdown failed" fi +elif [ "$testname" == "client/multiple_blocks.rb" ] +then + sleep 1 + if pgrep -f "$tw_srv_bin $srvcfg" + then + fail "Error: server still running rcon shutdown failed (2 blocks)" + fi + block1_ln="$(grep -n "block 1" "$ruby_logfile" | cut -d':' -f1)" + block2_ln="$(grep -n "block 2" "$ruby_logfile" | cut -d':' -f1)" + if [ "$block1_ln" == "" ] + then + fail "Error: 'block 1' not found in client log" + fi + if [ "$block2_ln" == "" ] + then + fail "Error: 'block 2' not found in client log" + fi + if [[ ! "$block1_ln" =~ ^[0-9]+$ ]] + then + fail "Error: failed to parse line number of 'block 1' got='$block1_ln'" + fi + if [[ ! "$block2_ln" =~ ^[0-9]+$ ]] + then + fail "Error: failed to parse line number of 'block 2' got='$block2_ln'" + fi + # ensure block call order matches definition order + if [ "$block1_ln" -gt "$block2_ln" ] + then + fail "Error: 'block 1' found after 'block 2' in client log" + fi else echo "Error: unkown test '$testname'" exit 1 diff --git a/lib/context.rb b/lib/context.rb new file mode 100644 index 0000000..aa896e7 --- /dev/null +++ b/lib/context.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Context + attr_reader :old_data, :client + attr_accessor :data + + def initialize(client, keys = {}) + @client = client + @cancle = false + @old_data = keys + @data = keys + end + + def verify + @data.each do |key, _value| + next if @old_data.key? key + + raise "Error: invalid data key '#{key}'\n valid keys: #{@old_data.keys}" + end + end + + def cancled? + @cancle + end + + def cancle + @cancle = true + end +end diff --git a/lib/game_client.rb b/lib/game_client.rb index 57d1980..d6d38da 100644 --- a/lib/game_client.rb +++ b/lib/game_client.rb @@ -3,34 +3,7 @@ require_relative 'models/player' require_relative 'models/chat_message' require_relative 'packer' - -class Context - attr_reader :old_data, :client - attr_accessor :data - - def initialize(client, keys = {}) - @client = client - @cancle = false - @old_data = keys - @data = keys - end - - def verify - @data.each do |key, _value| - next if @old_data.key? key - - raise "Error: invalid data key '#{key}'\n valid keys: #{@old_data.keys}" - end - end - - def cancled? - @cancle - end - - def cancle - @cancle = true - end -end +require_relative 'context' class GameClient attr_accessor :players, :pred_game_tick, :ack_game_tick @@ -42,6 +15,21 @@ class GameClient @pred_game_tick = 0 end + ## + # call_hook + # + # @param: hook_sym [Symbol] name of the symbol to call + # @param: context [Context] context object to pass on data + # @param: optional [Any] optional 2nd parameter passed to the callback + def call_hook(hook_sym, context, optional = nil) + @client.hooks[hook_sym].each do |hook| + hook.call(context, optional) + context.verify + return nil if context.cancled? + end + context + end + def on_client_info(chunk) # puts "Got playerinfo flags: #{chunk.flags}" u = Unpacker.new(chunk.data[1..]) @@ -61,11 +49,7 @@ class GameClient player:, chunk: ) - if @client.hooks[:client_info] - @client.hooks[:client_info].call(context) - context.verify - return if context.cancled? - end + return if call_hook(:client_info, context).nil? player = context.data[:player] @players[player.id] = player @@ -78,18 +62,14 @@ class GameClient silent = u.get_int context = Context.new( - @cliemt, + @client, player: @players[client_id], chunk:, client_id:, reason: reason == '' ? nil : reason, silent: silent != 0 ) - if @client.hooks[:client_drop] - @client.hooks[:client_drop].call(context) - context.verify - return if context.cancled? - end + return if call_hook(:client_drop, context).nil? @players.delete(context.data[:client_id]) end @@ -100,16 +80,13 @@ class GameClient def on_connected context = Context.new(@client) - if @client.hooks[:connected] - @client.hooks[:connected].call(context) - context.verify - return if context.cancled? - end + return if call_hook(:connected, context).nil? + @client.send_msg_start_info end def on_disconnect - @client.hooks[:disconnect]&.call + call_hook(:disconnect, Context.new(@client)) end def on_rcon_line(chunk) @@ -118,7 +95,7 @@ class GameClient @client, line: u.get_string ) - @client.hooks[:rcon_line]&.call(context) + call_hook(:rcon_line, context) end def on_snapshot(chunk) @@ -162,11 +139,8 @@ class GameClient def on_map_change(chunk) context = Context.new(@client, chunk:) - if @client.hooks[:map_change] - @client.hooks[:map_change].call(context) - context.verify - return if context.cancled? - end + return if call_hook(:map_change, context).nil? + # ignore mapdownload at all times # and claim to have the map @client.send_msg_ready @@ -183,6 +157,7 @@ class GameClient data[:author] = @players[data[:client_id]] msg = ChatMesage.new(data) - @client.hooks[:chat]&.call(msg) + context = Context.new(@client, chunk:) + call_hook(:chat, context, msg) end end diff --git a/lib/teeworlds_client.rb b/lib/teeworlds_client.rb index 8581d52..ecb75c8 100644 --- a/lib/teeworlds_client.rb +++ b/lib/teeworlds_client.rb @@ -23,7 +23,16 @@ class TeeworldsClient @state = NET_CONNSTATE_OFFLINE @ip = 'localhost' @port = 8303 - @hooks = {} + @hooks = { + chat: [], + map_change: [], + client_info: [], + client_drop: [], + connected: [], + disconnect: [], + rcon_line: [], + snapshot: [] + } @thread_running = false @signal_disconnect = false @game_client = GameClient.new(self) @@ -53,35 +62,35 @@ class TeeworldsClient end def on_chat(&block) - @hooks[:chat] = block + @hooks[:chat].push(block) end def on_map_change(&block) - @hooks[:map_change] = block + @hooks[:map_change].push(block) end def on_client_info(&block) - @hooks[:client_info] = block + @hooks[:client_info].push(block) end def on_client_drop(&block) - @hooks[:client_drop] = block + @hooks[:client_drop].push(block) end def on_connected(&block) - @hooks[:connected] = block + @hooks[:connected].push(block) end def on_disconnect(&block) - @hooks[:disconnect] = block + @hooks[:disconnect].push(block) end def on_rcon_line(&block) - @hooks[:rcon_line] = block + @hooks[:rcon_line].push(block) end def on_snapshot(&block) - @hooks[:snapshot] = block + @hooks[:snapshot].push(block) end def send_chat(str) diff --git a/scripts/hooks.sh b/scripts/hooks.sh index 40edb45..dae60af 100755 --- a/scripts/hooks.sh +++ b/scripts/hooks.sh @@ -144,9 +144,9 @@ function check_file() { { echo '# frozen_string_literal: true' echo '' - echo "require_relative '../../$ruby_file'" + echo "require_relative '../../${ruby_file::-3}'" echo "obj = $ruby_class.new" - echo "obj.$hook { |_| _ }" + echo "obj.$hook(&:verify)" } > "$tmpfile" if ! ruby "$tmpfile" &>/dev/null then