Skip to content

Commit

Permalink
WIP: add Niente, a 'do nothing' display service
Browse files Browse the repository at this point in the history
  • Loading branch information
noahgibbs committed Nov 9, 2023
1 parent 526faab commit 65ff440
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 1 deletion.
22 changes: 22 additions & 0 deletions lacci/lib/scarpe/niente.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

# Niente -- Italian for "nothing" -- is a null display service
# that doesn't display anything. It does very little, while
# keeping a certain amount of "mental model" or schema of
# how a real display service should act.
module Niente; end

require_relative "niente/logger"
Shoes::Log.instance = Niente::LogImpl.new

require_relative "niente/drawable"
require_relative "niente/app"
require_relative "niente/display_service"

if ENV["SHOES_SPEC_TEST"]
require_relative "niente/shoes_spec"
Shoes::Spec.instance = Niente::Test
end

Shoes::DisplayService.set_display_service_class(Niente::DisplayService)

23 changes: 23 additions & 0 deletions lacci/lib/scarpe/niente/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Niente
class App < Drawable
def initialize(properties)
super

bind_shoes_event(event_name: "init") { init }
bind_shoes_event(event_name: "run") { run }
bind_shoes_event(event_name: "destroy") { destroy }
end

def init
end

def run
send_shoes_event("wait", event_name: "custom_event_loop")
end

def destroy
end
end
end
61 changes: 61 additions & 0 deletions lacci/lib/scarpe/niente/display_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module Niente
# This is a "null" DisplayService, doing as little as it
# can get away with.
class DisplayService < Shoes::DisplayService
include Shoes::Log

class << self
attr_accessor :instance
end

def initialize
if Niente::DisplayService.instance
raise Scarpe::SingletonError, "ERROR! This is meant to be a singleton!"
end

Niente::DisplayService.instance = self

log_init("Niente::DisplayService")
super()
end

# Create a fake display drawable for a specific Shoes drawable, and pair it with
# the linkable ID for this Shoes drawable.
#
# @param drawable_class_name [String] The class name of the Shoes drawable, e.g. Shoes::Button
# @param drawable_id [String] the linkable ID for drawable events
# @param properties [Hash] a JSON-serialisable Hash with the drawable's Shoes styles
# @param is_widget [Boolean] whether the class is a user-defined Shoes::Widget subclass
# @return [Webview::Drawable] the newly-created Webview drawable
def create_display_drawable_for(drawable_class_name, drawable_id, properties, is_widget:)
existing = query_display_drawable_for(drawable_id, nil_ok: true)
if existing
@log.warn("There is already a display drawable for #{drawable_id.inspect}! Returning #{existing.class.name}.")
return existing
end

if drawable_class_name == "App"
@app = Niente::App.new(properties)
set_drawable_pairing(drawable_id, @app)

return @app
end

display_drawable = Niente::Drawable.new(properties)
set_drawable_pairing(drawable_id, display_drawable)

return display_drawable
end

# Destroy the display service and the app. Quit the process (eventually.)
#
# @return [void]
def destroy
@app.destroy
DisplayService.instance = nil
end
end
end

55 changes: 55 additions & 0 deletions lacci/lib/scarpe/niente/drawable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Niente
class Drawable < Shoes::Linkable
attr_reader :shoes_linkable_id
attr_reader :parent
attr_reader :children

def initialize(props)
@shoes_linkable_id = props.delete("shoes_linkable_id") || props.delete(:shoes_linkable_id)
@data = props

super(linkable_id: @shoes_linkable_id)

bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
display_parent = DisplayService.instance.query_display_drawable_for(new_parent_id)
if @parent != display_parent
set_parent(display_parent)
end
end

# When Shoes drawables change properties, we get a change notification here
bind_shoes_event(event_name: "prop_change", target: shoes_linkable_id) do |prop_changes|
prop_changes.each do |k, v|
instance_variable_set("@" + k, v)
end
properties_changed(prop_changes) if respond_to?(:properties_changed)
end

bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
end
end

def set_parent(new_parent)
@parent&.remove_child(self)
new_parent&.add_child(self)
@parent = new_parent
end

# Do not call directly, use set_parent
def remove_child(child)
@children ||= []
unless @children.include?(child)
STDERR.puts("remove_child: no such child(#{child.inspect}) for"\
" parent(#{parent.inspect})!")
end
@children.delete(child)
end

# Do not call directly, use set_parent
def add_child(child)
@children ||= []
@children << child
end
end
end

29 changes: 29 additions & 0 deletions lacci/lib/scarpe/niente/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require "shoes/log"
require "json"

module Niente; end
class Niente::LogImpl
include Shoes::Log # for constants

class PrintLogger
def initialize(_)
end

[:error, :warn, :debug, :info].each do |level|
define_method(level) do |msg|
puts "#{level}: #{msg}"
end
end
end

def logger_for_component(component)
PrintLogger.new(component.to_s)
end

def configure_logger(log_config)
end
end

#Shoes::Log.instance = Niente::LogImpl.new
92 changes: 92 additions & 0 deletions lacci/lib/scarpe/niente/shoes_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require "minitest"
require "scarpe/components/string_helpers"

module Niente; end

class Niente::Test
def self.run_shoes_spec_test_code(code, class_name: nil, test_name: nil)
if @shoes_spec_init
raise MultipleShoesSpecRunsError, "Scarpe-Webview can only run a single Shoes spec per process!"
end
@shoes_spec_init = true

require "scarpe/components/minitest_export_reporter"
Minitest::Reporters::ShoesExportReporter.activate!

class_name ||= ENV["SHOES_MINITEST_CLASS_NAME"] || "TestShoesSpecCode"
test_name ||= ENV["SHOES_MINITEST_METHOD_NAME"] || "test_shoes_spec"

Shoes::DisplayService.subscribe_to_event("heartbeat", nil) do
unless @hb_init
Minitest.run []
Shoes::App.instance.destroy
end
@hb_init = true
end

test_class = Class.new(Niente::ShoesSpecTest)
Object.const_set(Scarpe::Components::StringHelpers.camelize(class_name), test_class)
test_name = "test_" + test_name unless test_name.start_with?("test_")
test_class.define_method(test_name) do
STDERR.puts "Started running #{class_name.inspect}::#{test_name.inspect}"
eval(code)
end
end
end

class Niente::ShoesSpecTest < Minitest::Test
Shoes::Drawable.drawable_classes.each do |drawable_class|
finder_name = drawable_class.dsl_name

define_method(finder_name) do |*args|
app = Shoes::App.instance

drawables = app.find_drawables_by(drawable_class, *args)
raise Scarpe::MultipleDrawablesFoundError, "Found more than one #{finder_name} matching #{args.inspect}!" if drawables.size > 1
raise Scarpe::NoDrawablesFoundError, "Found no #{finder_name} matching #{args.inspect}!" if drawables.empty?

Niente::ShoesSpecProxy.new(drawables[0])
end
end
end

class Niente::ShoesSpecProxy
attr_reader :obj
attr_reader :linkable_id
attr_reader :display

JS_EVENTS = [:click, :hover, :leave, :change]

def initialize(obj)
@obj = obj
@linkable_id = obj.linkable_id
@display = ::Shoes::DisplayService.display_service.query_display_drawable_for(obj.linkable_id)
end

def method_missing(method, ...)
if @obj.respond_to?(method)
self.singleton_class.define_method(method) do |*args, **kwargs, &block|
@obj.send(method, *args, **kwargs, &block)
end
send(method, ...)
else
super # raise an exception
end
end

def trigger(event_name, *args)
# TODO
end

JS_EVENTS.each do |ev|
define_method "trigger_#{ev}" do |*args|
trigger(ev, *args)
end
end

def respond_to_missing?(method_name, include_private = false)
@obj.respond_to_missing?(method_name, include_private)
end
end
4 changes: 3 additions & 1 deletion lacci/lib/shoes/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ def run
case @event_loop_type
when "wait"
# Display lib wants us to busy-wait instead of it.
sleep 0.1 until @do_shutdown
until @do_shutdown
Shoes::DisplayService.dispatch_event("heartbeat", nil)
end
when "displaylib"
# If run event returned, that means we're done.
destroy
Expand Down

0 comments on commit 65ff440

Please sign in to comment.