Class: Lich::Common::SocketReadHook

Inherits:
Object
  • Object
show all
Defined in:
documented/common/socket_read_hook.rb

Overview

Read-only hooks that run immediately after a complete line is read from the game socket, before XML parsing, RawHook, DownstreamHook, or frontend writes. Hooks intentionally run inline, like the existing hook chains, to preserve exact reader-before-parser ordering.

Defined Under Namespace

Classes: Event

Class Method Summary collapse

Class Method Details

.add(name, action = nil) { ... } ⇒ String

Registers a proc to run on every complete line read from the game socket.

Hooks run inline before XML parsing and are invoked in registration order. If a hook raises, it is automatically removed and logged; execution continues with the next hook.

Examples:

Register with a proc

SocketReadHook.add("my_hook") { |line| puts line }

Register with an explicit proc

action = ->(line) { puts line }
SocketReadHook.add("my_hook", action)

Parameters:

  • name (String, Symbol)

    unique name for this hook; used with .remove

  • action (Proc, nil) (defaults to: nil)

    the hook proc; if nil, the block is used instead

Yields:

  • a Proc that receives arguments based on arity:

    • 0 args: hook.call (no data)
    • 1 arg: hook.call(server_string)
    • 2+ args: hook.call(server_string, event)

Returns:

  • (String)

    the hook name as a string

Raises:

  • (ArgumentError)

    if neither action nor block is a Proc



37
38
39
40
41
42
43
44
45
46
47
48
# File 'documented/common/socket_read_hook.rb', line 37

def self.add(name, action = nil, &block)
  action ||= block
  unless action.is_a?(Proc)
    raise ArgumentError, "SocketReadHook: not a Proc (#{action.inspect})"
  end

  @@mutex.synchronize do
    @@hook_sources[name.to_s] = current_source
    @@hooks[name.to_s] = action
  end
  name
end

.add_daemon_hook(name) { ... } ⇒ String

Registers a hook that persists for the daemon lifetime.

Equivalent to .add; provided for API clarity when hooks are added from daemon startup code.

Parameters:

  • name (String, Symbol)

    unique name for this hook

Yields:

  • a Proc to run on each socket read

Returns:

  • (String)

    the hook name as a string

See Also:



59
60
61
# File 'documented/common/socket_read_hook.rb', line 59

def self.add_daemon_hook(name, &block)
  add(name, &block)
end

.add_script_hook(name) { ... } ⇒ String

Registers a hook that removes itself when the calling script exits.

Calls .before_dying to schedule hook removal on script termination. No-op if before_dying is not available (e.g., in non-script contexts).

Parameters:

  • name (String, Symbol)

    unique name for this hook

Yields:

  • a Proc to run on each socket read

Returns:

  • (String)

    the hook name as a string

See Also:



72
73
74
75
76
# File 'documented/common/socket_read_hook.rb', line 72

def self.add_script_hook(name, &block)
  add(name, &block)
  before_dying { remove(name) } if defined?(before_dying)
  name
end

.hook_sourcesHash<String, String>

Returns a mapping of hook names to the source that registered them.

Source is the current script name, or 'core' for daemon/core hooks, or 'Unknown' if the source could not be determined.

Returns:



104
105
106
# File 'documented/common/socket_read_hook.rb', line 104

def self.hook_sources
  @@mutex.synchronize { @@hook_sources.dup }
end

.listArray<String>

Returns the names of all registered hooks.

Returns:

  • (Array<String>)

    hook names in registration order



94
95
96
# File 'documented/common/socket_read_hook.rb', line 94

def self.list
  @@mutex.synchronize { @@hooks.keys.dup }
end

.remove(name) ⇒ void

This method returns an undefined value.

Unregisters a hook by name.

Safe to call on non-existent hook names; no error is raised.

Parameters:

  • name (String, Symbol)

    the hook name



84
85
86
87
88
89
# File 'documented/common/socket_read_hook.rb', line 84

def self.remove(name)
  @@mutex.synchronize do
    @@hook_sources.delete(name.to_s)
    @@hooks.delete(name.to_s)
  end
end

.run(server_string, received_at: Time.now, monotonic_received_at: monotonic_now) ⇒ void

Note:

Intended to be called only by the socket reader, immediately after parsing a newline.

This method returns an undefined value.

Invokes all registered hooks with a newly-read server line.

Hooks are run synchronously and in-order. The server_string argument is frozen before passing to hooks to prevent accidental mutation. If any hook raises an exception, that hook is removed, the error is logged, and execution continues with the next hook.

Parameters:

  • server_string (String)

    a complete line read from the game socket

  • received_at (Time) (defaults to: Time.now)

    the wall-clock time the line was received (default: Time.now)

  • monotonic_received_at (Float) (defaults to: monotonic_now)

    the monotonic clock time in seconds (default: current monotonic time)



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'documented/common/socket_read_hook.rb', line 133

def self.run(server_string, received_at: Time.now, monotonic_received_at: monotonic_now)
  raw = server_string.dup.freeze
  event = Event.new(
    server_string: raw,
    received_at: received_at,
    monotonic_received_at: monotonic_received_at
  ).freeze

  entries.each do |name, action|
    invoke(action, raw, event)
  rescue StandardError => e
    remove(name)
    Lich.log "SocketReadHook #{name}: #{e.class}: #{e.message}\n\t#{e.backtrace&.first}"
  end
  nil
end

.sourcesvoid

This method returns an undefined value.

Prints a formatted table of all registered hooks and their sources to the foreground (via Lich::Messaging.mono).

Useful for debugging hook registration during development.



114
115
116
117
118
119
# File 'documented/common/socket_read_hook.rb', line 114

def self.sources
  info_table = Terminal::Table.new :headings => ['Hook', 'Source'],
                                   :rows     => hook_sources.to_a,
                                   :style    => { :all_separators => true }
  Lich::Messaging.mono(info_table.to_s)
end