Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Universal store model with bidirectional publisher-subscriber #741

Copy link
Copy link
Closed
@kushalkolar

Description

@kushalkolar
Issue body actions

I just got this idea which might allow us to create a universal model of the store model @clewis7 wrote. Not fully thought out but a starting point.

  1. Every publisher can be a subscriber, and every subscriber can be a publisher.
  2. The StoreModel receives or emits values of a certain type, call it the "store-data type".
  3. Publishers register not only a callback and the event type, but also a function that maps the callback output to the store-data type.
  4. Subscribers register a callback function which takes the subscriber and the store-data type.

Examples:

Sync multiple selectors:

def selection_changed(ev) -> float:
  # returns data type used by subscriber function set_selection
  return ev.selection

def set_selection(selector, new_value: float):
  # new_value is returned from selection_changed and used here
  selector.selection = new_value

for selector in [sel1, sel2, sel3]:
  # the first arg must be an object with an `add_event_handler` method
  store_model.add_publisher(selector, selection_changed, "selection")
  store_model.subscribe(selector, set_selection)

How this works internally:

# `StoreModel.add_publisher(obj, func_map_to_store, event_types)`
obj.add_event_handler(partial(StoreModel.notify_subscribers, obj, func_map_to_store), *event_types)

# `StoreModel.add_subscriber(obj, func_map_from_store)`
self._subscribers.add((obj, func_map_from_store))

# called by graphic/renderer when event happens
# `StoreModel.notify_subscribers(event_source, func_map_to_store, ev)`
new_value = func_map_to_store(ev)
# block events for all graphics
graphics = (sub[0] for sub in self.subscribers)
# call all subscribers
with pause_events(*graphics):
  for subscriber in self.subscribers:
    # unpack tuple
    obj, func = subscriber
    # skip if event source is this subscriber object
    if obj is event_source:
      continue
    # call subscriber func
    func(obj, new_value)

This idea can also be extended to have complex bidirectional events across event types since the functions allow to map to a central store-data-type and then inverse map to the subscriber function.

Decorator syntax would be nice here 😄

@store_model.add_publisher([sel1, sel2, sel3], "selection")
def selection_changed(ev) -> float:
  # returns data type used by subscriber function set_selection
  return ev.selection

@store_model.subscribe([sel1, sel2, sel3])
def set_selection(selector, new_value: float):
  # new_value is returned from selection_changed and used here
  selector.selection = new_value

What I like about this idea is that the inverse mapping from the store model data can be anything and they are easy to define:

# change image data using the store model
@store_model.subscribe([ig1, ig2, ig3])
def set_data(image_graphic, new_value: float):  # this store model only sends floats to subscribers
  vid = videos[image_graphic.name]
  image_graphic.data = vid[int(new_value)]

# update color at an index in a scatter or line graphic
@store_model.subscribe([sg1, sg2])
def scatter_index(scatter_graphic, new_value: float):
  reset_colors(scatter_graphic)
  scatter_graphic.colors[new_value] = "w"

Maybe we can even have StoreModel.value which is settable, allowing things from other libraries or user objects to interface with the StoreModel, such as ipywidgets or qt widgets.

StoreModel.value would just return the most recently set value to the store, which is of the data type that this store uses. When the value is set it just calls all the subscriber functions and gives them the new value.

We could have a StoreModel.add_event_handler() to allow registering arbitrary callbacks to external objects (such as an ipywidget object, or any external user object). Making these bidirectional through our StoreModel would be out of scope, it would have to be done on their end.

Should make use of our Graphic.deleted event to remove any subscriber/publisher when the graphic is deleted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.