From 1cdc1053774d8c218b5225a56c65127ac1c4636a Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Date: Mon, 27 Apr 2020 05:28:08 +0200 Subject: [PATCH] Refactored code to get it a little closer to being non-blocking. --- lib/aethyr/core/connection/server.rb | 5 +- lib/aethyr/core/connection/telnet.rb | 6 - lib/aethyr/core/render/display.rb | 533 ++++++++++++++------------- 3 files changed, 279 insertions(+), 265 deletions(-) diff --git a/lib/aethyr/core/connection/server.rb b/lib/aethyr/core/connection/server.rb index bda52f4..764e98b 100644 --- a/lib/aethyr/core/connection/server.rb +++ b/lib/aethyr/core/connection/server.rb @@ -63,15 +63,14 @@ module Aethyr #ready, _, _ = IO.select([listener]) socket, addr_info = listener.accept_nonblock(exception: false) if (not socket.nil?) and socket.is_a? Socket - did_something = true players << handle_client(socket, addr_info) end players.each do |player| - did_something = true if player.receive_data + player.display.set_term + player.receive_data end - sleep 0.1 unless did_something end # 4.times do # Adjust this number for the pool size diff --git a/lib/aethyr/core/connection/telnet.rb b/lib/aethyr/core/connection/telnet.rb index 71a8af2..570e7ea 100644 --- a/lib/aethyr/core/connection/telnet.rb +++ b/lib/aethyr/core/connection/telnet.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true require 'socket' require 'aethyr/core/connection/telnet_codes' @@ -64,10 +63,7 @@ class TelnetScanner end def process_iac - log 'doing process_iac' - @iac_state = :none if @iac_state.nil? - puts 'start' ch = nil? begin ch = @socket.recv_nonblock(1, Socket::MSG_PEEK) @@ -76,9 +72,7 @@ class TelnetScanner end return false if ch.nil? - puts 'stop' ch = ch.chr - log "processing #{ch.ord}" if @iac_state == :none && ch == IAC @socket.recv(1) @iac_state = :IAC diff --git a/lib/aethyr/core/render/display.rb b/lib/aethyr/core/render/display.rb index 080102c..f64003c 100644 --- a/lib/aethyr/core/render/display.rb +++ b/lib/aethyr/core/render/display.rb @@ -1,5 +1,6 @@ # coding: utf-8 -require "ncursesw" + +require 'ncursesw' require 'stringio' require 'aethyr/core/connection/telnet_codes' require 'aethyr/core/connection/telnet' @@ -21,17 +22,17 @@ class Display @color_settings = new_color_settings || to_default_colors - @socket = socket #StringIO.new + @socket = socket # StringIO.new @scanner = TelnetScanner.new(socket, self) @scanner.send_preamble - @screen = Ncurses.newterm("xterm-256color", @socket, @socket) + @screen = Ncurses.newterm('xterm-256color', @socket, @socket) Ncurses.set_term(@screen) Ncurses.cbreak # provide unbuffered input Ncurses.noecho # turn off input echoing Ncurses.nonl # turn off newline translation - Ncurses.curs_set(2) #high visibility cursor + Ncurses.curs_set(2) # high visibility cursor Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt Ncurses.stdscr.keypad(true) # turn on keypad mode @@ -39,13 +40,13 @@ class Display Ncurses.stdscr.clear @windows = { - :main => Window.new(@color_settings, buffered: true), - :input => Window.new(@color_settings), - :map => Window.new(@color_settings), - :look => Window.new(@color_settings, buffered: true), - :quick_bar => Window.new(@color_settings), - :status => Window.new(@color_settings), - :chat => Window.new(@color_settings, buffered: true) + main: Window.new(@color_settings, buffered: true), + input: Window.new(@color_settings), + map: Window.new(@color_settings), + look: Window.new(@color_settings, buffered: true), + quick_bar: Window.new(@color_settings), + status: Window.new(@color_settings), + chat: Window.new(@color_settings, buffered: true) } self.selected = :input layout @@ -54,11 +55,11 @@ class Display def init_colors Ncurses.start_color @use_color = true - @windows.each do |channel, window| + @windows.each do |_channel, window| window.enable_color end puts "There are #{Ncurses.COLORS} colors on this client" - Ncurses.assume_default_colors(Color::Foreground.attribute(:white), Color::Background.attribute(:black)); + Ncurses.assume_default_colors(Color::Foreground.attribute(:white), Color::Background.attribute(:black)) Ncurses.COLORS.times do |fg| Ncurses.COLORS.times do |bg| Ncurses.init_pair(fg + bg * Ncurses.COLORS, fg, bg) @@ -67,8 +68,8 @@ class Display update end - def selected= channel - @windows.each do |channel, window| + def selected=(channel) + @windows.each do |_channel, window| window.selected = false end @windows[channel].selected = true @@ -78,7 +79,7 @@ class Display @windows.each do |channel, window| return channel if window.selected end - return :input + :input end def layout(layout: @layout_type) @@ -86,25 +87,25 @@ class Display @layout_type = layout if @layout_type == :wide && @height >= 60 && @width >= 300 @windows[:quick_bar].create(height: 3, y: @height - 5) - @windows[:map].create(height: @height/2 + 1, width: 166) - @windows[:look].create(height: @height/2 - 3, width: 83, y: @height/2) - @windows[:main].create(height: @height/2 - 3, width: 83, x: 83, y: @height/2) + @windows[:map].create(height: @height / 2 + 1, width: 166) + @windows[:look].create(height: @height / 2 - 3, width: 83, y: @height / 2) + @windows[:main].create(height: @height / 2 - 3, width: 83, x: 83, y: @height / 2) @windows[:chat].create(height: @height - 4, width: @width - 249, x: 166) @windows[:status].create(height: @height - 4, x: @width - 83) elsif (@layout_type == :full || @layout_type == :wide) && @height >= 60 && @width >= 249 @windows[:quick_bar].create(height: 3, y: @height - 5) - @windows[:map].create(height: @height/2 + 1, width: 166) - @windows[:look].create(height: @height/2 - 3, width: 83, y: @height/2) - @windows[:main].create(height: @height/2 - 3, width: 83, x: 83, y: @height/2) + @windows[:map].create(height: @height / 2 + 1, width: 166) + @windows[:look].create(height: @height / 2 - 3, width: 83, y: @height / 2) + @windows[:main].create(height: @height / 2 - 3, width: 83, x: 83, y: @height / 2) @windows[:chat].create(height: @height - 4, x: 166) @windows[:status].destroy elsif (@layout_type == :partial || @layout_type == :full || @layout_type == :wide) && @height >= 60 && @width >= 166 @windows[:status].destroy @windows[:chat].destroy @windows[:quick_bar].create(height: 3, y: @height - 5) - @windows[:map].create(height: @height/2 + 1) - @windows[:look].create(height: @height/2 - 3, width: 83, y: @height/2) - @windows[:main].create(height: @height/2 - 3, x: 83, y: @height/2) + @windows[:map].create(height: @height / 2 + 1) + @windows[:look].create(height: @height / 2 - 3, width: 83, y: @height / 2) + @windows[:main].create(height: @height / 2 - 3, x: 83, y: @height / 2) else @windows[:map].destroy @windows[:look].destroy @@ -130,11 +131,6 @@ class Display layout end - def read_rdy? - ready, _, _ = IO.select([@socket]) - ready.any? - end - def echo? @echo end @@ -148,30 +144,29 @@ class Display end def recv - return nil unless read_rdy? - set_term recvd = read_line(0, 0) + return nil if recvd.nil? + puts "read returned: #{recvd}" recvd + "\n" end - def send_raw message + def send_raw(message) @socket.puts message end - def send (message, word_wrap = true, message_type: :main, internal_clear: false, add_newline: true) + def send(message, word_wrap = true, message_type: :main, internal_clear: false, add_newline: true) window = nil - if @windows[message_type].nil? || (not @windows[message_type].exists?) + if @windows[message_type].nil? || !@windows[message_type].exists? message_type = :main internal_clear = false end @windows[message_type].clear if internal_clear @windows[message_type].send(message, word_wrap, add_newline: add_newline) - end def close @@ -182,49 +177,49 @@ class Display Ncurses.endwin end - #Sets colors to defaults + # Sets colors to defaults def to_default_colors @color_settings = { - "roomtitle" => "fg:green bold", - "object" => "fg:blue", - "player" => "fg:cyan", - "mob" => "fg:yellow bold", - "merchant" => "fg:yellow dim", - "me" => "fg:white bold", - "exit" => "fg:green", - "say" => "fg:white bold", - "tell" => "fg:cyan bold", - "important" => "fg:red bold", - "editor" => "fg:cyan", - "news" => "fg:cyan bold", - "identifier" => "fg:magenta bold", - "water" => "fg:blue", - "waterlow" => "fg:blue dim", - "waterhigh" => "fg:blue bold", - "earth" => "fg:darkgoldenrod", - "earthlow" => "fg:darkgoldenrod dim", - "earthhigh" => "fg:darkgoldenrod bold", - "air" => "fg:white", - "airlow" => "fg:white dim", - "airhigh" => "fg:white bold", - "fire" => "fg:red", - "firelow" => "fg:red dim", - "firehigh" => "fg:red bold", - "regular" => "fg:white bg:black" + 'roomtitle' => 'fg:green bold', + 'object' => 'fg:blue', + 'player' => 'fg:cyan', + 'mob' => 'fg:yellow bold', + 'merchant' => 'fg:yellow dim', + 'me' => 'fg:white bold', + 'exit' => 'fg:green', + 'say' => 'fg:white bold', + 'tell' => 'fg:cyan bold', + 'important' => 'fg:red bold', + 'editor' => 'fg:cyan', + 'news' => 'fg:cyan bold', + 'identifier' => 'fg:magenta bold', + 'water' => 'fg:blue', + 'waterlow' => 'fg:blue dim', + 'waterhigh' => 'fg:blue bold', + 'earth' => 'fg:darkgoldenrod', + 'earthlow' => 'fg:darkgoldenrod dim', + 'earthhigh' => 'fg:darkgoldenrod bold', + 'air' => 'fg:white', + 'airlow' => 'fg:white dim', + 'airhigh' => 'fg:white bold', + 'fire' => 'fg:red', + 'firelow' => 'fg:red dim', + 'firehigh' => 'fg:red bold', + 'regular' => 'fg:white bg:black' } end - #Sets the foreground color for a given setting. + # Sets the foreground color for a given setting. def set_color(code, color) - code.downcase! unless code.nil? - color.downcase! unless color.nil? + code&.downcase! + color&.downcase! - if not @color_settings.has_key? code + if !@color_settings.key? code "No such setting: #{code}" else - if not @use_color + unless @use_color @color_settings.keys.each do |setting| - @color_settings[setting] = "" + @color_settings[setting] = '' end @use_color = true end @@ -234,248 +229,274 @@ class Display end end - #Returns list of color settings to show the player + # Returns list of color settings to show the player def show_color_config - <<-CONF -Colors are currently: #{@use_color ? "Enabled" : "Disabled"} -Text Setting Color ------------------------------------------------ -Room Title roomtitle <roomtitle>#{@color_settings['roomtitle']}</roomtitle> -Object object <object>#{@color_settings['object']}</object> -Player player <player>#{@color_settings['player']}</player> -Mob mob <mob>#{@color_settings['mob']}</mob> -Merchant merchant <merchant>#{@color_settings['merchant']}</merchant> -Me me <me>#{@color_settings['me']}</me> -Exit exit <exit>#{@color_settings['exit']}</exit> -Say say <say>#{@color_settings['say']}</say> -Tell tell <tell>#{@color_settings['tell']}</tell> -Important important <important>#{@color_settings['important']}</important> -Editor editor <editor>#{@color_settings['editor']}</editor> -News news <news>#{@color_settings['news']}</news> -Identifier identifier <identifier>#{@color_settings['identifier']}</identifier> -Fire fire <fire>#{@color_settings['fire']}</fire> -Fire when low firelow <firelow>#{@color_settings['firelow']}</firelow> -Fire when high firehigh <firehigh>#{@color_settings['firehigh']}</firehigh> -Air air <air>#{@color_settings['air']}</air> -Air when low airlow <airlow>#{@color_settings['airlow']}</airlow> -Air when high airhigh <airhigh>#{@color_settings['airhigh']}</airhigh> -Water water <water>#{@color_settings['water']}</water> -Water when low waterlow <waterlow>#{@color_settings['waterlow']}</waterlow> -Water when high waterhigh <waterhigh>#{@color_settings['waterhigh']}</waterhigh> -Earth earth <earth>#{@color_settings['earth']}</earth> -Earth when low earthlow <earthlow>#{@color_settings['earthlow']}</earthlow> -Earth when high earthhigh <earthhigh>#{@color_settings['earthhigh']}</earthhigh> -Regular regular #{@color_settings['regular']} -CONF + <<~CONF + Colors are currently: #{@use_color ? 'Enabled' : 'Disabled'} + Text Setting Color + ----------------------------------------------- + Room Title roomtitle <roomtitle>#{@color_settings['roomtitle']}</roomtitle> + Object object <object>#{@color_settings['object']}</object> + Player player <player>#{@color_settings['player']}</player> + Mob mob <mob>#{@color_settings['mob']}</mob> + Merchant merchant <merchant>#{@color_settings['merchant']}</merchant> + Me me <me>#{@color_settings['me']}</me> + Exit exit <exit>#{@color_settings['exit']}</exit> + Say say <say>#{@color_settings['say']}</say> + Tell tell <tell>#{@color_settings['tell']}</tell> + Important important <important>#{@color_settings['important']}</important> + Editor editor <editor>#{@color_settings['editor']}</editor> + News news <news>#{@color_settings['news']}</news> + Identifier identifier <identifier>#{@color_settings['identifier']}</identifier> + Fire fire <fire>#{@color_settings['fire']}</fire> + Fire when low firelow <firelow>#{@color_settings['firelow']}</firelow> + Fire when high firehigh <firehigh>#{@color_settings['firehigh']}</firehigh> + Air air <air>#{@color_settings['air']}</air> + Air when low airlow <airlow>#{@color_settings['airlow']}</airlow> + Air when high airhigh <airhigh>#{@color_settings['airhigh']}</airhigh> + Water water <water>#{@color_settings['water']}</water> + Water when low waterlow <waterlow>#{@color_settings['waterlow']}</waterlow> + Water when high waterhigh <waterhigh>#{@color_settings['waterhigh']}</waterhigh> + Earth earth <earth>#{@color_settings['earth']}</earth> + Earth when low earthlow <earthlow>#{@color_settings['earthlow']}</earthlow> + Earth when high earthhigh <earthhigh>#{@color_settings['earthhigh']}</earthhigh> + Regular regular #{@color_settings['regular']} + CONF end def refresh_watch_windows(player) if @windows[:look].exists? room = $manager.get_object(player.container) - if not room.nil? + if !room.nil? look_text = room.look(player) - #cleared = false + # cleared = false msg = Window.split_message(look_text, 79).join("\n") send(msg, message_type: :look, internal_clear: true, add_newline: true) else - send("Nothing to look at.", message_type: :look, internal_clear: true) + send('Nothing to look at.', message_type: :look, internal_clear: true) end end if @windows[:map].exists? room = $manager.get_object(player.container) - if not room.nil? + if !room.nil? send(room.area.render_map(player, room.area.position(room)), message_type: :map, internal_clear: true) else - send("No map of current area.", message_type: :map, internal_clear: true) + send('No map of current area.', message_type: :map, internal_clear: true) end end if @windows[:quick_bar].exists? - send("this is the quick bar", message_type: :quick_bar, internal_clear: true, add_newline: false) + send('this is the quick bar', message_type: :quick_bar, internal_clear: true, add_newline: false) end if @windows[:status].exists? - send("This is the status window", message_type: :status, internal_clear: true, add_newline: false) + send('This is the status window', message_type: :status, internal_clear: true, add_newline: false) end end + def set_term + Ncurses.set_term(@screen) + end + private def update @windows.each do |channel, window| - window.update unless channel == self.selected + window.update unless channel == selected end - @windows[self.selected].update unless @windows[self.selected].nil? #make sure the selected window always takes the last update so border renders properly - Ncurses.doupdate() - end - - def set_term - Ncurses.set_term(@screen) + @windows[selected].update # make sure the selected window always takes the last update so border renders properly + Ncurses.doupdate end - def read_line(y, x, - max_len: (@windows[:input].window_text.getmaxx - x - 1), - string: "", - cursor_pos: 0) - escape = nil - loop do + max_len: (@windows[:input].window_text.getmaxx - x - 1)) + + @read_stage = :none if @read_stage.nil? + if @read_stage == :none + @escape = nil + @cursor_pos = 0 + @input_buffer = '' + @read_stage = :update + end + + if @read_stage == :update @windows[:input].clear - @windows[:input].window_text.mvaddstr(y,x,string) if echo? - @windows[:input].window_text.move(y,x+cursor_pos) if echo? - #update + @windows[:input].window_text.mvaddstr(y, x, @input_buffer) if echo? + @windows[:input].window_text.move(y, x + @cursor_pos) if echo? + # update @windows[:input].update - Ncurses.doupdate(); + Ncurses.doupdate - next if not @scanner.process_iac + @read_stage = :iac + return nil + end - ch = @windows[:input].window_text.getch - puts ch + if @read_stage == :iac + @read_stage = :input if @scanner.process_iac + return nil + end - unless escape.nil? - case escape + ch = @windows[:input].window_text.getch + @read_stage = :iac + puts ch - when [27] - case ch - when 91 - escape = [27, 91] - end + unless @escape.nil? + case @escape - when [27, 91] - case ch - when 53 #scroll up - escape = [27, 91, 53] - when 54 #scroll down - escape = [27, 91, 54] - when 68 - ch = Ncurses::KEY_LEFT - escape = nil - when 67 - ch = Ncurses::KEY_RIGHT - escape = nil - when 65 - ch = Ncurses::KEY_UP - escape = nil - when 66 - ch = Ncurses::KEY_DOWN - escape = nil - else - escape = nil - next - end + when [27] + case ch + when 91 + @escape = [27, 91] + end - when [27, 91, 53] - case ch - when 126 #page up - if self.selected == :input - @windows[:main].buffer_pos += 5 - else - @windows[self.selected].buffer_pos +=5 if (@windows[self.selected].respond_to? :buffer_pos) && (not @windows[self.selected].buffer_pos.nil?) - end - escape = nil - next - else - escape = nil - next - end + when [27, 91] + case ch + when 53 # scroll up + @escape = [27, 91, 53] + when 54 # scroll down + @escape = [27, 91, 54] + when 68 + ch = Ncurses::KEY_LEFT + @escape = nil + when 67 + ch = Ncurses::KEY_RIGHT + @escape = nil + when 65 + ch = Ncurses::KEY_UP + @escape = nil + when 66 + ch = Ncurses::KEY_DOWN + @escape = nil + else + @escape = nil + return nil + end - when [27, 91, 54] - case ch - when 126 #page down - if self.selected == :input - @windows[:main].buffer_pos -= 5 - else - @windows[self.selected].buffer_pos -= 5 if (@windows[self.selected].respond_to? :buffer_pos) && (not @windows[self.selected].buffer_pos.nil?) - end - escape = nil - next + when [27, 91, 53] + case ch + when 126 # page up + if selected == :input + @windows[:main].buffer_pos += 5 else - escape = nil - next + if (@windows[selected].respond_to? :buffer_pos) && !@windows[selected].buffer_pos.nil? + @windows[selected].buffer_pos += 5 + end end + @escape = nil + return nil else - escape = nil - next + @escape = nil + return nil end - end - if escape.nil? + when [27, 91, 54] case ch - when 27 - escape = [27] - when Ncurses::KEY_LEFT - cursor_pos = [0, cursor_pos-1].max - when Ncurses::KEY_RIGHT - cursor_pos = [max_len, cursor_pos + 1, string.length].min - when Ncurses::KEY_UP - @windows[:main].buffer_pos += 1 - when Ncurses::KEY_DOWN - @windows[:main].buffer_pos -= 1 - when 13 # return - @windows[:input].clear - self.selected = :input - @windows[:main].send("\n≫≫≫≫≫ #{string}\n\n") if echo? - @windows[:main].buffer_pos = 0 - @windows[:main].update - return string#, cursor_pos, ch # Which return key has been used? - #when Ncurses::KEY_BACKSPACE - when 127, "\b".ord, Ncurses::KEY_BACKSPACE # backspace - #string = string[0...([0, cursor_pos-1].max)] + string[cursor_pos..-1] - if cursor_pos >= 1 - string.slice!(cursor_pos - 1) - cursor_pos = cursor_pos-1 - end -# window.mvaddstr(y, x+string.length, " ") if echo? - @selected = :input - # when etc... - when 32..255 # remaining printables - if (cursor_pos < max_len) - #string[cursor_pos,0] = ch - string.insert(cursor_pos, ch.chr) - cursor_pos += 1 - end - @selected = :input - when 9 # tab - case self.selected - when :input - if @windows[:look].exists? - self.selected = :look - else - self.selected = :main - end - when :main - if @windows[:chat].exists? - self.selected = :chat - elsif @windows[:status].exists? - self.selected = :status - else - self.selected = :input - end - when :map - self.selected = :main - when :look - if @windows[:map].exists? - self.selected = :map - else - self.selected = :input - end - when :chat - if @windows[:status].exists? - self.selected = :status - else - self.selected = :input - end - when :status - self.selected = :input + when 126 # page down + if selected == :input + @windows[:main].buffer_pos -= 5 else - self.selected = :input + if (@windows[selected].respond_to? :buffer_pos) && !@windows[selected].buffer_pos.nil? + @windows[selected].buffer_pos -= 5 + end end - update + @escape = nil + return nil else - log "Unidentified key press: #{ch}" + @escape = nil + return nil + end + else + @escape = nil + return nil + end + end + + if @escape.nil? + case ch + when 27 + @escape = [27] + when Ncurses::KEY_LEFT + @cursor_pos = [0, @cursor_pos - 1].max + @read_stage = :update + when Ncurses::KEY_RIGHT + @cursor_pos = [max_len, @cursor_pos + 1, @input_buffer.length].min + @read_stage = :update + when Ncurses::KEY_UP + @windows[:main].buffer_pos += 1 + @read_stage = :update + when Ncurses::KEY_DOWN + @windows[:main].buffer_pos -= 1 + @read_stage = :update + when 13 # return + @windows[:input].clear + self.selected = :input + @windows[:main].send("\n≫≫≫≫≫ #{@input_buffer}\n\n") if echo? + @windows[:main].buffer_pos = 0 + @windows[:main].update + @read_stage = :none + return @input_buffer # , cursor_pos, ch # Which return key has been used? + # when Ncurses::KEY_BACKSPACE + when 127, "\b".ord, Ncurses::KEY_BACKSPACE # backspace + # string = string[0...([0, cursor_pos-1].max)] + string[cursor_pos..-1] + if @cursor_pos >= 1 + @input_buffer.slice!(@cursor_pos - 1) + @cursor_pos -= 1 end + # window.mvaddstr(y, x+string.length, " ") if echo? + @selected = :input + @read_stage = :update + # when etc... + when 32..255 # remaining printables + if @cursor_pos < max_len + # string[cursor_pos,0] = ch + @input_buffer.insert(@cursor_pos, ch.chr) + @cursor_pos += 1 + end + @selected = :input + @read_stage = :update + when 9 # tab + self.selected = case selected + when :input + if @windows[:look].exists? + :look + else + :main + end + when :main + if @windows[:chat].exists? + :chat + elsif @windows[:status].exists? + :status + else + :input + end + when :map + :main + when :look + if @windows[:map].exists? + :map + else + :input + end + when :chat + if @windows[:status].exists? + :status + else + :input + end + when :status + :input + else + :input + end + @read_stage = :update + update + else + log "Unidentified key press: #{ch}" end end + + return nil end end -- GitLab