Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: standardised rooms code #59

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
43 changes: 43 additions & 0 deletions modules/aca/rooms/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

load File.join(__dir__, 'component_manager.rb')

module Aca; end
module Aca::Rooms; end

class Aca::Rooms::Base
include ::Orchestrator::Constants
include ::Aca::Rooms::ComponentManager

generic_name :System
implements :logic

def self.setting(hash)
previous = @default_settings || {}
default_settings previous.merge hash
end

# ------------------------------
# Callbacks

def on_load
on_update
end

def on_update
self[:name] = system.name
self[:type] = self.class.name.demodulize

@config = Hash.new do |h, k|
h[k] = setting(k) || default_setting[k]
end
end

protected

def default_setting
self.class.instance_variable_get :@default_settings
end

attr_reader :config
end
20 changes: 20 additions & 0 deletions modules/aca/rooms/collab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

::Orchestrator::DependencyManager.load('Aca::Rooms::Base', :logic)

class Aca::Rooms::Collab < Aca::Rooms::Base
descriptive_name 'ACA Collaboration Space'
description <<~DESC
Logic and external control API for collaboration spaces.

Collaboration spaces are rooms / systems where the design is centered
around a VC system, with the primary purpose of collaborating with both
people in room, as well as remote parties.
DESC

components :Power, :Io

def on_update
super
end
end
152 changes: 152 additions & 0 deletions modules/aca/rooms/component_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# frozen_string_literal: true

module Aca; end
module Aca::Rooms; end

module Aca::Rooms::Components; end

module Aca::Rooms::ComponentManager
module Composer
# Mini-DSL for defining cross-component behaviours.
#
# With the block provided `before`, `during`, or `after` can be used
# to insert additional behaviour to methods that should only exist when
# both components are in use. The result of the original method will
# be preserved, but will be deferred until the completion of any
# composite behaviours.
def compose_with(component, &extensions)
overlay = overlay_module_for component

hooks = {}

# Eval the block to populate hooks with the overlay actions as
# method => { position => [actions] }
composer = Class.new do
[:before, :during, :after].each do |position|
define_method position do |method, &action|
((hooks[method] ||= {})[position] ||= []) << action
end
end
end

composer.new.instance_eval(&extensions)

# Build the overlay Module for prepending
hooks.each do |method, actions|
# FIXME: this removes visibility of original args
overlay.send :define_method, method do |*args|
result = nil

sequence = [
actions[:before],
[
proc { |*x| result = super(*x) },
*actions[:during]
],
actions[:after]
].compact!

exec_actions = sequence.reduce(thread.finally) do |a, b|
a.then do
thread.all(b.map { |x| instance_exec(*args, &x) })
end
end

exec_actions.then { result }
end
end

self
end

private

# Build out a module heirachy so that <base>::Compositions::<other>
# exists and can be used to house any behaviour extensions to be
# applied when both components are in use.
def overlay_module_for(component)
[:Compositions, component].reduce(self) do |context, name|
if context.const_defined? name, false
context.const_get name
else
context.const_set name, Module.new
end
end
end
end

module Mixin
def components(*components)
component_settings = {}

# Load the associated module
modules = components.map do |component|
mod, settings = load component

component_settings.merge! settings do |key, _, _|
raise "setting \"#{key}\" declared in multiple components"
end

mod
end

# Include the components
include(*modules)

# Compose cross-component behaviours
overlays = modules.flat_map do |base|
next unless base.const_defined? :Compositions, false

components.each_with_object([]) do |component, compositions|
next unless base::Compositions.const_defined? component
compositions << base::Compositions.const_get(component)
end
end
prepend(*overlays.compact)

# Bubble up settings definitions
setting component_settings
end

private

# Load a component, returning a reference to the Module and the
# settings it defines.
#
# Settings may be defined by using the 'setting' keyword within the
# component module. Support for this is temporarily mixed in during
# load below.
def load(component)
fqn = "::Aca::Rooms::Components::#{component}"

settings = {}

mod = if ::Aca::Rooms::Components.const_defined? component
::Aca::Rooms::Components.const_get component
else
::Aca::Rooms::Components.const_set component, Module.new
end

# Inject a `setting` class method that pushes back into settings
# here via a closure. This enables any settings used by the
# component to be declared as `setting key: <default>`.
mod.define_singleton_method :setting do |hash|
settings.merge! hash do |key, _, _|
raise "\"#{key}\" declared multiple times in #{fqn}"
end
end

mod.extend Composer

::Orchestrator::DependencyManager.load fqn, :logic

[mod, settings]
end
end

module_function

def included(other)
other.extend Mixin
end
end
40 changes: 40 additions & 0 deletions modules/aca/rooms/components/io.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module Aca::Rooms::Components::Io
setting default_routes: {}

def show(source, on: default_outputs)
source = source.to_sym
target = Array(on).map(&:to_sym)

logger.debug "Showing #{source} on #{target.join ','}"

connect source => target
end

protected

def connect(signal_map)
logger.debug 'called connect'
end

def blank(outputs)
logger.debug 'called blank'
end

def default_outputs
[]
end
end

# Aca::Rooms::Components::Io.extend ::Aca::Rooms::ComponentManager::Composer

Aca::Rooms::Components::Io.compose_with :Power do
during :powerup do
connect {} # config.default_routes
end

during :shutdown do
connect {}
end
end
16 changes: 16 additions & 0 deletions modules/aca/rooms/components/power.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Aca::Rooms::Components::Power
setting powerup_actions: {}

setting shutdown_actions: {}

def powerup
logger.debug 'in power mod'
:online
end

def shutdown
:shutdown
end
end