77 lines
2.6 KiB
Plaintext
77 lines
2.6 KiB
Plaintext
require "math"
|
|
require "time"
|
|
|
|
# this enum allows us to specify what type of message the proc_chan received.
|
|
# this trivial example only has one action, but more enum members can be added
|
|
# to update the proc, or take other actions
|
|
enum Action
|
|
Finished # we've waited long enough, and are asking for our result
|
|
# Update # potential member representing an update to the integrator function
|
|
end
|
|
|
|
class Integrator
|
|
property interval : Float64
|
|
getter s : Float64 = 0f64
|
|
|
|
# initialize our k function as a proc that takes a float and just returns 0
|
|
getter k : Proc(Float64, Float64) = ->(t : Float64) { 0f64 }
|
|
|
|
# channels used for communicating with the main fiber
|
|
@proc_chan : Channel(Tuple(Action, Proc(Float64, Float64)|Nil))
|
|
@result_chan : Channel(Float64)
|
|
|
|
def initialize(@k, @proc_chan, @result_chan, @interval = 1e-4)
|
|
# use a monotonic clock for accuracy
|
|
start = Time.monotonic.total_seconds
|
|
t0, k0 = 0f64, @k.call(0f64)
|
|
|
|
loop do
|
|
# this sleep returns control to the main fiber. if the main fiber hasn't finished sleeping,
|
|
# control will be returned to this loop
|
|
sleep interval.seconds
|
|
# check the channel to see if the function has changed
|
|
self.check_channel()
|
|
t1 = Time.monotonic.total_seconds - start
|
|
k1 = @k.call(t1)
|
|
@s += (k1 + k0) * (t1 - t0) / 2.0
|
|
t0, k0 = t1, k1
|
|
end
|
|
end
|
|
|
|
# check the proc_chan for messages, update the integrator function or send the result as needed
|
|
def check_channel
|
|
select
|
|
when message = @proc_chan.receive
|
|
action, new_k = message
|
|
case action
|
|
when Action::Finished
|
|
@result_chan.send @s
|
|
@k = new_k unless new_k.nil?
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
|
|
# this channel allows us to update the integrator function,
|
|
# and inform the integrator to send the result over the result channel
|
|
proc_chan = Channel(Tuple(Action, Proc(Float64, Float64)|Nil)).new
|
|
|
|
# channel used to return the result from the integrator
|
|
result_chan = Channel(Float64).new
|
|
|
|
# run everything in a new top-level fiber to avoid shared memory issues.
|
|
# since the fiber immediately sleeps, control is returned to the main code.
|
|
# the main code then sleeps for two seconds, returning control to our state_clock fiber.
|
|
# when two seconds is up, this state_clock fiber will return control
|
|
# to the main code on the next `sleep interval.seconds`
|
|
spawn name: "state_clock" do
|
|
ai = Integrator.new ->(t : Float64) { Math.sin(Math::PI * t) }, proc_chan, result_chan
|
|
end
|
|
|
|
sleep 2.seconds
|
|
proc_chan.send({Action::Finished, ->(t : Float64) { 0f64 }})
|
|
sleep 0.5.seconds
|
|
puts result_chan.receive
|