Skip to content

Commit 1d6ea73

Browse files
committed
Merge pull request #73 from jdantonio/actress
Actress merge
2 parents ab03fae + 7ce883c commit 1d6ea73

40 files changed

+982
-91
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
22

33
gemspec
44

5+
56
group :development do
67
gem 'rake', '~> 10.2.2'
78
gem 'countloc', '~> 0.4.0', platforms: :mri

lib/concurrent.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
require 'concurrent/supervisor'
2929
require 'concurrent/timer_task'
3030
require 'concurrent/tvar'
31+
require 'concurrent/actress'
3132

3233
# Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
3334
# F#, C#, Java, and classic concurrency patterns.

lib/concurrent/actress.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require 'concurrent/configuration'
2+
require 'concurrent/executor/one_by_one'
3+
require 'concurrent/ivar'
4+
require 'concurrent/logging'
5+
6+
module Concurrent
7+
8+
# {include:file:lib/concurrent/actress/doc.md}
9+
module Actress
10+
11+
require 'concurrent/actress/type_check'
12+
require 'concurrent/actress/errors'
13+
require 'concurrent/actress/core_delegations'
14+
require 'concurrent/actress/envelope'
15+
require 'concurrent/actress/reference'
16+
require 'concurrent/actress/core'
17+
require 'concurrent/actress/context'
18+
19+
require 'concurrent/actress/ad_hoc'
20+
21+
# @return [Reference, nil] current executing actor if any
22+
def self.current
23+
Thread.current[:__current_actress__]
24+
end
25+
26+
# implements ROOT
27+
class Root
28+
include Context
29+
# to allow spawning of new actors, spawn needs to be called inside the parent Actor
30+
def on_message(message)
31+
if message.is_a?(Array) && message.first == :spawn
32+
spawn message[1], &message[2]
33+
else
34+
# ignore
35+
end
36+
end
37+
end
38+
39+
# A root actor, a default parent of all actors spawned outside an actor
40+
ROOT = Core.new(parent: nil, name: '/', class: Root).reference
41+
42+
# @param block for actress_class instantiation
43+
# @param args see {.spawn_optionify}
44+
def self.spawn(*args, &block)
45+
if Actress.current
46+
Core.new(spawn_optionify(*args).merge(parent: Actress.current), &block).reference
47+
else
48+
ROOT.ask([:spawn, spawn_optionify(*args), block]).value
49+
end
50+
end
51+
52+
# as {.spawn} but it'll raise when Actor not initialized properly
53+
def self.spawn!(*args, &block)
54+
spawn(spawn_optionify(*args).merge(initialized: ivar = IVar.new), &block).tap { ivar.no_error! }
55+
end
56+
57+
# @overload spawn_optionify(actress_class, name, *args)
58+
# @param [Context] actress_class to be spawned
59+
# @param [String, Symbol] name of the instance, it's used to generate the path of the actor
60+
# @param args for actress_class instantiation
61+
# @overload spawn_optionify(opts)
62+
# see {Core#initialize} opts
63+
def self.spawn_optionify(*args)
64+
if args.size == 1 && args.first.is_a?(Hash)
65+
args.first
66+
else
67+
{ class: args[0],
68+
name: args[1],
69+
args: args[2..-1] }
70+
end
71+
end
72+
end
73+
end

lib/concurrent/actress/ad_hoc.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Concurrent
2+
module Actress
3+
class AdHoc
4+
include Context
5+
def initialize(*args, &initializer)
6+
@on_message = Type! initializer.call(*args), Proc
7+
end
8+
9+
def on_message(message)
10+
instance_exec message, &@on_message
11+
end
12+
end
13+
end
14+
end

lib/concurrent/actress/context.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
module Concurrent
2+
module Actress
3+
4+
# module used to define actor behaviours
5+
# @example ping
6+
# class Ping
7+
# include Context
8+
# def on_message(message)
9+
# message
10+
# end
11+
# end
12+
#
13+
# Ping.spawn(:ping1).ask(:m).value #=> :m
14+
module Context
15+
include TypeCheck
16+
include CoreDelegations
17+
18+
attr_reader :core
19+
20+
# @abstract override to define Actor's behaviour
21+
# @param [Object] message
22+
# @return [Object] a result which will be used to set the IVar supplied to Reference#ask
23+
# @note self should not be returned (or sent to other actors), {#reference} should be used
24+
# instead
25+
def on_message(message)
26+
raise NotImplementedError
27+
end
28+
29+
def logger
30+
core.logger
31+
end
32+
33+
# @api private
34+
def on_envelope(envelope)
35+
@envelope = envelope
36+
on_message envelope.message
37+
ensure
38+
@envelope = nil
39+
end
40+
41+
# @see Actress.spawn
42+
def spawn(*args, &block)
43+
Actress.spawn(*args, &block)
44+
end
45+
46+
# @see Core#children
47+
def children
48+
core.children
49+
end
50+
51+
# @see Core#terminate!
52+
def terminate!
53+
core.terminate!
54+
end
55+
56+
private
57+
58+
# @api private
59+
def initialize_core(core)
60+
@core = Type! core, Core
61+
end
62+
63+
# @return [Envelope] current envelope, accessible inside #on_message processing
64+
def envelope
65+
@envelope or raise 'envelope not set'
66+
end
67+
68+
def self.included(base)
69+
base.extend ClassMethods
70+
super base
71+
end
72+
73+
module ClassMethods
74+
# behaves as {Actress.spawn} but class_name is omitted
75+
def spawn(name_or_opts, *args, &block)
76+
Actress.spawn spawn_optionify(name_or_opts, *args), &block
77+
end
78+
79+
# behaves as {Actress.spawn!} but class_name is omitted
80+
def spawn!(name_or_opts, *args, &block)
81+
Actress.spawn! spawn_optionify(name_or_opts, *args), &block
82+
end
83+
84+
private
85+
86+
def spawn_optionify(name_or_opts, *args)
87+
if name_or_opts.is_a? Hash
88+
name_or_opts.merge class: self
89+
else
90+
{ class: self, name: name_or_opts, args: args }
91+
end
92+
end
93+
end
94+
end
95+
end
96+
end

0 commit comments

Comments
 (0)