Problem 300381 · hard · Level 03 Linear Management & Searching

A Message Board for Functions

py-closures · py-higher-order · py-exceptions · callbacks · publish-subscribe

Parts of a game talk to each other through a message board: a part subscribes a handler function to a topic, and whenever someone publishes a message on that topic, the handler is called with the message's payload.

Write make_bus() that returns a pair of functions (subscribe, publish) sharing one board:

  • subscribe(topic, handler) registers handler for topic and returns a function unsubscribe(). Calling it removes that one registration; calling it again does nothing. Subscribing the same handler twice makes two registrations. The topic "*" receives every message.
  • publish(topic, payload) calls every handler registered for topic or for "*", in the order in which they were subscribed, and returns a list with one entry per call: the handler's return value, or, if the handler raised an exception, the string "error: " followed by the exception's class name. A failing handler does not stop the others.
  • The handlers that receive a message are decided when publish starts: a handler that subscribes or unsubscribes during a publish changes only later publishes. A handler may itself publish; that inner publish works the same way.

The tests use the helper run_bus(make_bus, script), which plays a list of steps (("sub", id, topic, spec), ("unsub", id), ("pub", topic, payload)) and returns the results of the "pub" steps. The handler specs are described in the helper's docstring; help(run_bus) prints it with Run.

Examples

Input:  run_bus(make_bus, [("sub", 1, "score", "double"), ("sub", 2, "*", ("tag", "log")),
                           ("pub", "score", 5), ("pub", "chat", "hi"), ("unsub", 1), ("pub", "score", 7)])
Output: [[10, "log:5"], ["log:hi"], ["log:7"]]

Input:  run_bus(make_bus, [("sub", 1, "t", "fail"), ("sub", 2, "t", "echo"), ("pub", "t", 3)])
Output: [["error: RuntimeError", 3]]

Input:  run_bus(make_bus, [("sub", 1, "t", ("unsub", 2)), ("sub", 2, "t", "echo"),
                           ("pub", "t", "a"), ("pub", "t", "b")])
Output: [["unsubscribed", "a"], ["unsubscribed"]]
Explanation: handler 2 was already chosen for "a"; it is gone for "b".

Constraints

  • Up to 2000 registrations and 2000 publishes.
  • Handlers are any functions of one argument; they may raise any exception derived from Exception.

Goals

  • Return several functions that share one piece of hidden state
  • Return a function that undoes one specific registration
  • Keep one failing callback from breaking the others, and decide what a callback may change while it runs
Starting Python…