From 0e890d5de3744d5483b5e3a1571ec01598482a03 Mon Sep 17 00:00:00 2001 From: Thomas Crescenzi Date: Wed, 25 Jun 2025 22:15:48 -0400 Subject: [PATCH] upgrade to erlang and otp v1 --- README.md | 8 +- gleam.toml | 6 +- manifest.toml | 18 +-- src/chip.gleam | 55 ++++---- test/artifacts/clock.gleam | 49 +++---- test/artifacts/game.gleam | 42 +++--- test/artifacts/pubsub.gleam | 5 +- test/artifacts/store.gleam | 7 +- test/benchmark.ex | 8 +- test/chip_test.gleam | 167 +++++++++++++----------- test/guides/as-local-pubsub.md | 32 +++-- test/guides/as-part-of-supervisor.md | 106 ++++++--------- test/guides/as-subject-index.md | 15 +-- test/guides/as_local_pubsub.gleam | 26 ++-- test/guides/as_part_of_supervisor.gleam | 106 ++++++--------- test/guides/as_subject_index.gleam | 8 +- test/guides/readme.gleam | 8 +- 17 files changed, 317 insertions(+), 349 deletions(-) diff --git a/README.md b/README.md index 9a92c02..c09c451 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ pub fn main() { let assert Ok(session_b) = game.start(FireDice) let assert Ok(session_c) = game.start(PlayChip) - chip.register(registry, GroupA, session_a) - chip.register(registry, GroupB, session_b) - chip.register(registry, GroupA, session_c) + chip.register(registry.data, GroupA, session_a.data) + chip.register(registry.data, GroupB, session_b.data) + chip.register(registry.data, GroupA, session_c.data) chip.members(registry, GroupA, 50) - |> list.each(fn(session) { game.next(session) }) + |> list.each(fn(session) { game.next(session.data) }) } ``` diff --git a/gleam.toml b/gleam.toml index 5136804..489cd99 100644 --- a/gleam.toml +++ b/gleam.toml @@ -19,9 +19,9 @@ pages = [ [dependencies] gleam_stdlib = ">= 0.36.0 and < 2.0.0" -gleam_erlang = ">= 0.24.0 and < 2.0.0" -gleam_otp = ">= 0.10.0 and < 2.0.0" -lamb = ">= 0.1.0 and < 2.0.0" +gleam_erlang = ">= 1.0.0 and < 2.0.0" +gleam_otp = ">= 1.0.0 and < 2.0.0" +lamb = { git = "https://github.com/trescenzi/lamb.git", ref = "9b9b0e2" } [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 61e656f..bca0a8d 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,20 +2,20 @@ # You typically do not need to edit this file packages = [ - { name = "benchee", version = "1.3.1", build_tools = ["mix"], requirements = ["deep_merge", "statistex", "table"], otp_app = "benchee", source = "hex", outer_checksum = "76224C58EA1D0391C8309A8ECBFE27D71062878F59BD41A390266BF4AC1CC56D" }, + { name = "benchee", version = "1.4.0", build_tools = ["mix"], requirements = ["deep_merge", "statistex", "table"], otp_app = "benchee", source = "hex", outer_checksum = "299CD10DD8CE51C9EA3DDB74BB150F93D25E968F93E4C1FA31698A8E4FA5D715" }, { name = "deep_merge", version = "1.0.0", build_tools = ["mix"], requirements = [], otp_app = "deep_merge", source = "hex", outer_checksum = "CE708E5F094B9CD4E8F2BE4F00D2F4250C4095BE93F8CD6D018C753894885430" }, - { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, - { name = "gleam_otp", version = "0.16.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "FA0EB761339749B4E82D63016C6A18C4E6662DA05BAB6F1346F9AF2E679E301A" }, - { name = "gleam_stdlib", version = "0.47.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3B22D46743C46498C8355365243327AC731ECD3959216344FA9CF9AD348620AC" }, - { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, - { name = "lamb", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "lamb", source = "hex", outer_checksum = "A74714DE60B3BADB623DFFF910C843793AE660222A9AD63C70053D33C0C3D311" }, + { name = "gleam_erlang", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "D7A2E71CE7F6B513E62F9A9EF6DFDE640D9607598C477FCCADEF751C45FD82E7" }, + { name = "gleam_otp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "7020E652D18F9ABAC9C877270B14160519FA0856EE80126231C505D719AD68DA" }, + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, + { name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" }, + { name = "lamb", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], source = "git", repo = "https://github.com/trescenzi/lamb.git", commit = "9b9b0e2593213f0f94a0950a1193a810a57f1f1b" }, { name = "statistex", version = "1.0.0", build_tools = ["mix"], requirements = [], otp_app = "statistex", source = "hex", outer_checksum = "FF9D8BEE7035028AB4742FF52FC80A2AA35CECE833CF5319009B52F1B5A86C27" }, ] [requirements] benchee = { version = ">= 1.3.0 and < 2.0.0" } -gleam_erlang = { version = ">= 0.24.0 and < 2.0.0" } -gleam_otp = { version = ">= 0.10.0 and < 2.0.0" } +gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.0.0 and < 2.0.0" } gleam_stdlib = { version = ">= 0.36.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -lamb = { version = ">= 0.1.0 and < 2.0.0" } +lamb = { git = "https://github.com/trescenzi/lamb.git", ref = "9b9b0e2" } diff --git a/src/chip.gleam b/src/chip.gleam index 3d0f479..2d15b5e 100644 --- a/src/chip.gleam +++ b/src/chip.gleam @@ -3,10 +3,9 @@ //// automatically delist dead processes. import gleam/dynamic -import gleam/erlang import gleam/erlang/atom import gleam/erlang/process.{type Pid, type Subject} -import gleam/function +import gleam/erlang/reference.{type Reference} import gleam/otp/actor import gleam/result.{try} import lamb.{Bag, Private, Protected, Public, Set} @@ -47,9 +46,12 @@ pub type Named { /// ```gleam /// > let _ = chip.start(chip.Named("sessions")) /// ``` -pub fn start(named: Named) -> Result(Registry(msg, group), actor.StartError) { - let init = fn() { init(named) } - actor.start_spec(actor.Spec(init: init, init_timeout: 100, loop: loop)) +pub fn start(named: Named) + -> Result(actor.Started(Subject(Message(a, b))), actor.StartError) { + let init = fn(self) { init(self, named) } + actor.new_with_initialiser(100, init) + |> actor.on_message(loop) + |> actor.start() } /// Retrieves a previously named registry. @@ -139,7 +141,7 @@ pub fn members( group: group, timeout: Int, ) -> List(Subject(msg)) { - let group_store = process.call(registry, GroupStore2(_), timeout) + let group_store = process.call(registry, timeout, GroupStore2) lamb.lookup(group_store, group) } @@ -159,7 +161,7 @@ pub fn stop(registry: Registry(msg, group)) -> Nil { // Server Code :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: type Monitor = - erlang.Reference + Reference /// Chip's internal message type. pub opaque type Message(msg, group) { @@ -185,10 +187,8 @@ type ProcessDown { ProcessDown(monitor: Monitor, pid: Pid) } -fn init( - named: Named, -) -> actor.InitResult(State(msg, group), Message(msg, group)) { - let self = process.new_subject() +fn init(self, named: Named) { + //let self = process.new_subject() let table = initialize_named_registries_store() @@ -206,16 +206,19 @@ fn init( let selector = process.new_selector() - |> process.selecting(self, function.identity) - |> process.selecting_anything(process_down) + |> process.select(self) + |> process.select_other(process_down) - actor.Ready(state, selector) + actor.initialised(state) + |> actor.selecting(selector) + |> actor.returning(self) + |> Ok } fn loop( - message: Message(msg, group), state: State(msg, group), -) -> actor.Next(Message(msg, group), State(msg, group)) { + message: Message(msg, group), +) -> actor.Next(State(msg, group), Message(msg, group)) { case message { GroupStore2(client) -> { // priority is given through selective receive @@ -228,9 +231,15 @@ fn loop( Register(subject, group) -> { let pid = process.subject_owner(subject) - let Nil = monitor(state.monitors, pid) - lamb.insert(state.monitors, pid, Nil) - lamb.insert(state.groups, group, subject) + case pid { + Ok(pid) -> { + let Nil = monitor(state.monitors, pid) + lamb.insert(state.monitors, pid, Nil) + lamb.insert(state.groups, group, subject) + } + Error(_) -> Nil + //TODO what do we want to do in this case? + } state |> actor.continue() @@ -256,7 +265,7 @@ fn loop( } Stop -> { - actor.Stop(process.Normal) + actor.stop() } } } @@ -311,7 +320,7 @@ fn monitor(monitors: lamb.Table(Pid, Nil), pid: Pid) -> Nil { case lamb.any(monitors, pid) { True -> Nil False -> { - let _monitor = process.monitor_process(pid) + let _monitor = process.monitor(pid) Nil } } @@ -321,7 +330,7 @@ fn monitor(monitors: lamb.Table(Pid, Nil), pid: Pid) -> Nil { fn decode_down_message(message: dynamic.Dynamic) -> Result(ProcessDown, Nil) fn schedulers() -> Int { - ffi_system_info(atom.create_from_string("schedulers")) + ffi_system_info(atom.create("schedulers")) } type Option = @@ -331,7 +340,7 @@ type Option = fn ffi_system_info(option: Option) -> Int fn demonitor(reference: Monitor) -> Nil { - let _ = ffi_demonitor(reference, [atom.create_from_string("flush")]) + let _ = ffi_demonitor(reference, [atom.create("flush")]) Nil } diff --git a/test/artifacts/clock.gleam b/test/artifacts/clock.gleam index 6daae25..8432b3a 100644 --- a/test/artifacts/clock.gleam +++ b/test/artifacts/clock.gleam @@ -1,9 +1,6 @@ import chip import gleam/erlang/process -import gleam/function.{identity} -import gleam/option import gleam/otp/actor -import gleam/otp/supervisor pub opaque type Message { Inc @@ -18,19 +15,10 @@ pub type Group { } pub fn start(registry: chip.Registry(Message, Group), group: Group, count: Int) { - let init = fn() { init(registry, group, count) } - actor.start_spec(actor.Spec(init: init, init_timeout: 10, loop: loop)) -} - -pub fn childspec(count) { - supervisor.worker(fn(param) { - let #(registry, group) = param - start(registry, group, count) - }) - |> supervisor.returning(fn(param, _self) { - let #(registry, group) = param - #(registry, group, count) - }) + let init = fn(self) { init(self, registry, group, count) } + actor.new_with_initialiser(10, init) + |> actor.on_message(loop) + |> actor.start() } pub fn stop(counter: process.Subject(Message)) -> Nil { @@ -42,38 +30,39 @@ pub fn increment(counter: process.Subject(Message)) -> Nil { } pub fn current(counter: process.Subject(Message)) -> Int { - actor.call(counter, Current(_), 10) + actor.call(counter, 10, Current) } -fn init(registry: chip.Registry(Message, Group), group: Group, count: Int) { - // Create a reference to self - let self = process.new_subject() - +fn init(self, registry: chip.Registry(Message, Group), group: Group, count: Int) { // Register the counter under an id on initialization chip.register(registry, group, self) + let selector = + process.new_selector() + |> process.select(self) + // The registry may send messages through the self subject to this actor // adding self to this actor selector will allow us to handle those messages. - actor.Ready( - count, - process.new_selector() - |> process.selecting(self, identity), - ) + + actor.initialised(count) + |> actor.selecting(selector) + |> actor.returning(self) + |> Ok } -fn loop(message: Message, count: Int) { +fn loop(count: Int, message: Message) { case message { Inc -> { - actor.Continue(count + 1, option.None) + actor.continue(count + 1) } Current(client) -> { process.send(client, count) - actor.Continue(count, option.None) + actor.continue(count) } Stop -> { - actor.Stop(process.Normal) + actor.stop() } } } diff --git a/test/artifacts/game.gleam b/test/artifacts/game.gleam index 3c603e1..396737c 100644 --- a/test/artifacts/game.gleam +++ b/test/artifacts/game.gleam @@ -1,8 +1,7 @@ import chip import gleam/erlang/process -import gleam/function import gleam/otp/actor -import gleam/otp/supervisor +import gleam/otp/supervision as supervisor type SessionRegistry = chip.Registry(Message, Int) @@ -27,17 +26,20 @@ pub type Session { } pub fn start(action) { - actor.start(action, loop) + actor.new(action) + |> actor.on_message(loop) + |> actor.start } -pub fn start_with(registry: SessionRegistry, id: Int, action: Action) { - let init = fn() { init(registry, id, action) } - actor.start_spec(actor.Spec(init: init, init_timeout: 10, loop: loop)) +pub fn start_with(id: Int, registry: SessionRegistry, action: Action) { + let init = fn(self) { init(self, registry, id, action) } + actor.new_with_initialiser(10, init) + |> actor.on_message(loop) + |> actor.start } -pub fn childspec(registry: SessionRegistry, action) { - supervisor.worker(fn(id) { start_with(registry, id, action) }) - |> supervisor.returning(fn(id, _self) { id + 1 }) +pub fn childspec(id: Int, registry: SessionRegistry, action) { + supervisor.worker(fn() { start_with(id, registry, action) }) } pub fn next(game: Game) -> Nil { @@ -45,34 +47,34 @@ pub fn next(game: Game) -> Nil { } pub fn current(game: Game) -> String { - actor.call(game, Current(_), 100) + actor.call(game, 100, Current) } pub fn stop(game: Game) -> Nil { actor.send(game, Stop) } -fn init(registry, id, action) { - // Create a reference to self - let self = process.new_subject() - +fn init(self, registry, id, action) { // Register the counter under an id on initialization chip.register(registry, id, self) // The registry may send messages through the self subject to this actor // adding self to this actor selector will allow us to handle those messages. - actor.Ready( - action, + let selector = process.new_selector() - |> process.selecting(self, function.identity), - ) + |> process.select(self) + + actor.initialised(action) + |> actor.selecting(selector) + |> actor.returning(self) + |> Ok } -fn loop(message, action) { +fn loop(action, message) { case message { Next -> next_state(action) Current(client) -> send_unicode(client, action) - Stop -> actor.Stop(process.Normal) + Stop -> actor.stop() } } diff --git a/test/artifacts/pubsub.gleam b/test/artifacts/pubsub.gleam index 8da4ac0..b64a35a 100644 --- a/test/artifacts/pubsub.gleam +++ b/test/artifacts/pubsub.gleam @@ -1,7 +1,7 @@ import chip import gleam/erlang/process import gleam/list -import gleam/otp/supervisor +import gleam/otp/supervision pub type PubSub(message, channel) = chip.Registry(message, channel) @@ -11,8 +11,7 @@ pub fn start() { } pub fn childspec() { - supervisor.worker(fn(_param) { start() }) - |> supervisor.returning(fn(_param, pubsub) { pubsub }) + supervision.worker(fn() { start() }) } pub fn subscribe( diff --git a/test/artifacts/store.gleam b/test/artifacts/store.gleam index 3abe849..4282bbe 100644 --- a/test/artifacts/store.gleam +++ b/test/artifacts/store.gleam @@ -1,7 +1,6 @@ import chip import gleam/erlang/process -import gleam/otp/actor -import gleam/otp/supervisor +import gleam/otp/supervision pub type Store(message) = chip.Registry(message, Int) @@ -9,12 +8,12 @@ pub type Store(message) = pub type Id = Int -pub fn start() -> Result(Store(message), actor.StartError) { +pub fn start() { chip.start(chip.Unnamed) } pub fn childspec() { - supervisor.worker(fn(_index) { start() }) + supervision.worker(start) } pub fn index(store: Store(message), id: Id, subject: process.Subject(message)) { diff --git a/test/benchmark.ex b/test/benchmark.ex index d052abd..11f6be3 100644 --- a/test/benchmark.ex +++ b/test/benchmark.ex @@ -16,7 +16,7 @@ defmodule Chip.Benchmark do }, inputs: inputs, before_scenario: fn quantity -> - {:ok, registry} = @chip.start(:unnamed) + {:ok, {:started, _pid, registry}} = @chip.start(:unnamed) initialize_registry(registry, quantity) registry end, @@ -54,7 +54,7 @@ defmodule Chip.Benchmark do end defp subject_info(subject) do - pid = @process.subject_owner(subject) + {:ok, pid} = @process.subject_owner(subject) [{:monitors, monitors}, {:memory, memory}, {:message_queue_len, length}] = :erlang.process_info(pid, [ @@ -79,7 +79,7 @@ defmodule Chip.Benchmark.Memory do size = unit_measurement() IO.puts(" Unit of measurement: #{size}") - {:ok, registry} = @chip.start(:unnamed) + {:ok, {:started, _pid, registry}} = @chip.start(:unnamed) IO.puts("\n--- Rough memory measurements ---\n") @@ -120,7 +120,7 @@ defmodule Chip.Benchmark.Memory do end defp subject_info(subject) do - pid = @process.subject_owner(subject) + {:ok, pid} = @process.subject_owner(subject) process_info(pid) end diff --git a/test/chip_test.gleam b/test/chip_test.gleam index 0e84fe9..a02dfb0 100644 --- a/test/chip_test.gleam +++ b/test/chip_test.gleam @@ -2,7 +2,8 @@ import artifacts/game.{DrawCard, FireDice, PlayChip} import chip import gleam/erlang/process import gleam/list -import gleam/otp/supervisor +import gleam/otp/actor +import gleam/otp/static_supervisor import gleam/result import gleeunit import gleeunit/should @@ -20,8 +21,11 @@ pub fn cannot_retrieve_a_non_existing_registry_test() { pub fn can_retrieve_records_from_a_named_registry_test() { let assert Ok(registry) = chip.start(chip.Named("game-sessions")) + let subject = registry.data - let register = fn(session) { chip.register(registry, Nil, session) } + let register = fn(session: actor.Started(process.Subject(game.Message))) { + chip.register(subject, Nil, session.data) + } let _ = game.start(DrawCard) |> result.map(register) let _ = game.start(DrawCard) |> result.map(register) @@ -35,6 +39,7 @@ pub fn can_retrieve_records_from_a_named_registry_test() { pub fn can_retrieve_subjects_from_group_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data let assert Ok(session_1) = game.start(DrawCard) let assert Ok(session_2) = game.start(DrawCard) @@ -43,60 +48,64 @@ pub fn can_retrieve_subjects_from_group_test() { let assert Ok(session_5) = game.start(DrawCard) let assert Ok(session_6) = game.start(DrawCard) - session_1 |> chip.register(registry, RoomA, _) - session_2 |> chip.register(registry, RoomB, _) - session_3 |> chip.register(registry, RoomB, _) - session_4 |> chip.register(registry, RoomC, _) - session_5 |> chip.register(registry, RoomC, _) - session_6 |> chip.register(registry, RoomC, _) + session_1.data |> chip.register(subject, RoomA, _) + session_2.data |> chip.register(subject, RoomB, _) + session_3.data |> chip.register(subject, RoomB, _) + session_4.data |> chip.register(subject, RoomC, _) + session_5.data |> chip.register(subject, RoomC, _) + session_6.data |> chip.register(subject, RoomC, _) - let assert [_] = chip.members(registry, RoomA, 50) - let assert [_, _] = chip.members(registry, RoomB, 50) - let assert [_, _, _] = chip.members(registry, RoomC, 50) + let assert [_] = chip.members(subject, RoomA, 50) + let assert [_, _] = chip.members(subject, RoomB, 50) + let assert [_, _, _] = chip.members(subject, RoomC, 50) } pub fn can_retrieve_same_subject_from_different_groups_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data let assert Ok(session) = game.start(DrawCard) - session |> chip.register(registry, RoomA, _) - session |> chip.register(registry, RoomB, _) - session |> chip.register(registry, RoomC, _) + session.data |> chip.register(subject, RoomA, _) + session.data |> chip.register(subject, RoomB, _) + session.data |> chip.register(subject, RoomC, _) - let assert [session_a] = chip.members(registry, RoomA, 50) - let assert [session_b] = chip.members(registry, RoomB, 50) - let assert [session_c] = chip.members(registry, RoomC, 50) - should.be_true(session == session_a) + let assert [session_a] = chip.members(subject, RoomA, 50) + let assert [session_b] = chip.members(subject, RoomB, 50) + let assert [session_c] = chip.members(subject, RoomC, 50) + should.be_true(session.data == session_a) should.be_true(session_a == session_b && session_b == session_c) } pub fn can_retrieve_individual_subjects_of_same_process_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data - process.new_subject() |> chip.register(registry, Nil, _) - process.new_subject() |> chip.register(registry, Nil, _) - process.new_subject() |> chip.register(registry, Nil, _) + process.new_subject() |> chip.register(subject, Nil, _) + process.new_subject() |> chip.register(subject, Nil, _) + process.new_subject() |> chip.register(subject, Nil, _) - let assert [_, _, _] = chip.members(registry, Nil, 50) + let assert [_, _, _] = chip.members(subject, Nil, 50) } pub fn cannot_retrieve_duplicate_subjects_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data let self = process.new_subject() - self |> chip.register(registry, Nil, _) - self |> chip.register(registry, Nil, _) - self |> chip.register(registry, Nil, _) + self |> chip.register(subject, Nil, _) + self |> chip.register(subject, Nil, _) + self |> chip.register(subject, Nil, _) - let assert [_] = chip.members(registry, Nil, 50) + let assert [_] = chip.members(subject, Nil, 50) } //*---------------- dispatch tests --------------*// pub fn dispatch_is_applied_over_subjects_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data let assert Ok(session_1) = game.start(DrawCard) let assert Ok(session_2) = game.start(PlayChip) @@ -105,27 +114,34 @@ pub fn dispatch_is_applied_over_subjects_test() { let assert Ok(session_5) = game.start(FireDice) let assert Ok(session_6) = game.start(FireDice) - session_1 |> chip.register(registry, Nil, _) - session_2 |> chip.register(registry, Nil, _) - session_3 |> chip.register(registry, Nil, _) - session_4 |> chip.register(registry, Nil, _) - session_5 |> chip.register(registry, Nil, _) - session_6 |> chip.register(registry, Nil, _) + session_1.data |> chip.register(subject, Nil, _) + session_2.data |> chip.register(subject, Nil, _) + session_3.data |> chip.register(subject, Nil, _) + session_4.data |> chip.register(subject, Nil, _) + session_5.data |> chip.register(subject, Nil, _) + session_6.data |> chip.register(subject, Nil, _) - chip.members(registry, Nil, 50) + chip.members(subject, Nil, 50) |> list.each(game.next) // wait for game session operation to finish - let assert True = until(fn() { game.current(session_1) }, is: "🪙", for: 50) - let assert True = until(fn() { game.current(session_2) }, is: "🎲", for: 50) - let assert True = until(fn() { game.current(session_3) }, is: "🎲", for: 50) - let assert True = until(fn() { game.current(session_4) }, is: "🂡", for: 50) - let assert True = until(fn() { game.current(session_5) }, is: "🂡", for: 50) - let assert True = until(fn() { game.current(session_6) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_1.data) }, is: "🪙", for: 50) + let assert True = + until(fn() { game.current(session_2.data) }, is: "🎲", for: 50) + let assert True = + until(fn() { game.current(session_3.data) }, is: "🎲", for: 50) + let assert True = + until(fn() { game.current(session_4.data) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_5.data) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_6.data) }, is: "🂡", for: 50) } pub fn dispatch_is_applied_over_groups_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data let assert Ok(session_1) = game.start(DrawCard) let assert Ok(session_2) = game.start(DrawCard) @@ -134,23 +150,23 @@ pub fn dispatch_is_applied_over_groups_test() { let assert Ok(session_5) = game.start(DrawCard) let assert Ok(session_6) = game.start(DrawCard) - session_1 |> chip.register(registry, RoomA, _) - session_2 |> chip.register(registry, RoomB, _) - session_3 |> chip.register(registry, RoomB, _) - session_4 |> chip.register(registry, RoomC, _) - session_5 |> chip.register(registry, RoomC, _) - session_6 |> chip.register(registry, RoomC, _) + session_1.data |> chip.register(subject, RoomA, _) + session_2.data |> chip.register(subject, RoomB, _) + session_3.data |> chip.register(subject, RoomB, _) + session_4.data |> chip.register(subject, RoomC, _) + session_5.data |> chip.register(subject, RoomC, _) + session_6.data |> chip.register(subject, RoomC, _) - chip.members(registry, RoomA, 50) + chip.members(subject, RoomA, 50) |> list.each(game.next) - chip.members(registry, RoomB, 50) + chip.members(subject, RoomB, 50) |> list.each(fn(subject) { game.next(subject) game.next(subject) }) - chip.members(registry, RoomC, 50) + chip.members(subject, RoomC, 50) |> list.each(fn(subject) { game.next(subject) game.next(subject) @@ -158,12 +174,18 @@ pub fn dispatch_is_applied_over_groups_test() { }) // wait for game session operation to finish - let assert True = until(fn() { game.current(session_1) }, is: "🪙", for: 50) - let assert True = until(fn() { game.current(session_2) }, is: "🎲", for: 50) - let assert True = until(fn() { game.current(session_3) }, is: "🎲", for: 50) - let assert True = until(fn() { game.current(session_4) }, is: "🂡", for: 50) - let assert True = until(fn() { game.current(session_5) }, is: "🂡", for: 50) - let assert True = until(fn() { game.current(session_6) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_1.data) }, is: "🪙", for: 50) + let assert True = + until(fn() { game.current(session_2.data) }, is: "🎲", for: 50) + let assert True = + until(fn() { game.current(session_3.data) }, is: "🎲", for: 50) + let assert True = + until(fn() { game.current(session_4.data) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_5.data) }, is: "🂡", for: 50) + let assert True = + until(fn() { game.current(session_6.data) }, is: "🂡", for: 50) } //*---------------- other tests ------------------*// @@ -172,52 +194,45 @@ pub fn subject_eventually_deregisters_after_process_dies_test() { let assert Ok(registry) = chip.start(chip.Unnamed) let assert Ok(session) = game.start(DrawCard) - chip.register(registry, "my-game", session) + chip.register(registry.data, "my-game", session.data) // stops the game session actor - game.stop(session) + game.stop(session.data) // eventually the game session should be automatically de-registered - let find = fn() { chip.members(registry, "my-game", 50) } + let find = fn() { chip.members(registry.data, "my-game", 50) } let assert True = until(find, is: [], for: 50) } pub fn registering_works_along_supervisor_test() { let assert Ok(registry) = chip.start(chip.Unnamed) + let subject = registry.data - let assert Ok(_supervisor) = - supervisor.start_spec( - supervisor.Spec( - argument: 1, - max_frequency: 5, - frequency_period: 1, - init: fn(children) { - children - |> supervisor.add(game.childspec(registry, DrawCard)) - |> supervisor.add(game.childspec(registry, PlayChip)) - |> supervisor.add(game.childspec(registry, FireDice)) - }, - ), - ) + let _supervisor = + static_supervisor.new(static_supervisor.OneForOne) + |> static_supervisor.add(game.childspec(1, subject, DrawCard)) + |> static_supervisor.add(game.childspec(2, subject, PlayChip)) + |> static_supervisor.add(game.childspec(3, subject, FireDice)) + |> static_supervisor.start() // assert we can retrieve individual subjects - let assert [session_1] = chip.members(registry, 1, 50) + let assert [session_1] = chip.members(subject, 1, 50) let assert "🂡" = game.current(session_1) - let assert [session_2] = chip.members(registry, 2, 50) + let assert [session_2] = chip.members(subject, 2, 50) let assert "🪙" = game.current(session_2) - let assert [session_3] = chip.members(registry, 3, 50) + let assert [session_3] = chip.members(subject, 3, 50) let assert "🎲" = game.current(session_3) // assert we're not able to retrieve non-registered subjects - let assert [] = chip.members(registry, 4, 50) + let assert [] = chip.members(subject, 4, 50) // assert subject is restarted by the supervisor after actor dies game.stop(session_2) let different_subject = fn() { - case chip.members(registry, 2, 50) { + case chip.members(subject, 2, 50) { [session] if session != session_2 -> True _other -> False } diff --git a/test/guides/as-local-pubsub.md b/test/guides/as-local-pubsub.md index dd5857f..40bdf6f 100644 --- a/test/guides/as-local-pubsub.md +++ b/test/guides/as-local-pubsub.md @@ -9,7 +9,7 @@ This pattern would be a generic way of re-defining chip as a pubsub system: import chip import gleam/erlang/process import gleam/list -import gleam/otp/supervisor +import gleam/otp/supervision pub type PubSub(message, channel) = chip.Registry(message, channel) @@ -19,8 +19,7 @@ pub fn start() { } pub fn childspec() { - supervisor.worker(fn(_param) { start() }) - |> supervisor.returning(fn(_param, pubsub) { pubsub }) + supervision.worker(fn() { start() }) } pub fn subscribe( @@ -39,6 +38,7 @@ pub fn publish( chip.members(pubsub, channel, 50) |> list.each(fn(subscriber) { process.send(subscriber, message) }) } + ``` It may be used to wire-up applications that require reacting to events. For example, @@ -50,7 +50,6 @@ import chip import gleam/erlang/process import gleam/int import gleam/list -import gleam/otp/task pub type Channel { General @@ -64,27 +63,28 @@ pub type Event { pub fn main() { let assert Ok(pubsub) = pubsub.start() + let pubsub_subject = pubsub.data // For this scenario, out of simplicity, all clients are the current process. let client = process.new_subject() // Client is interested in coffee and pets. - chip.register(pubsub, Coffee, client) - chip.register(pubsub, Pets, client) + chip.register(pubsub_subject, Coffee, client) + chip.register(pubsub_subject, Pets, client) // Lets assume this is the server process broadcasting a welcome message. - task.async(fn() { - chip.members(pubsub, General, 50) + process.spawn(fn() { + chip.members(pubsub_subject, General, 50) |> list.each(fn(client) { Event(id: 1, message: "Welcome to General! Follow rules and be nice.") |> process.send(client, _) }) - chip.members(pubsub, Coffee, 50) + chip.members(pubsub_subject, Coffee, 50) |> list.each(fn(client) { Event(id: 2, message: "Ice breaker! Favorite cup of coffee?") |> process.send(client, _) }) - chip.members(pubsub, Pets, 50) + chip.members(pubsub_subject, Pets, 50) |> list.each(fn(client) { Event(id: 3, message: "Pets!") |> process.send(client, _) @@ -104,7 +104,10 @@ pub fn main() { }) } -fn listen_for_messages(client, messages) -> List(String) { +fn listen_for_messages( + client: process.Subject(Event), + messages: List(String), +) -> List(String) { // This function will listen until messages stop arriving for 100 milliseconds. // A selector is useful to transform our Events into types a client expects, @@ -112,14 +115,15 @@ fn listen_for_messages(client, messages) -> List(String) { // events into strings with the `to_string` function. let selector = process.new_selector() - |> process.selecting(client, to_string) + |> process.select_map(client, to_string) - case process.select(selector, 100) { - Ok(message) -> + case process.selector_receive(selector, 100) { + Ok(message) -> { // A message was received, capture it and attempt to listen for another message. message |> list.prepend(messages, _) |> listen_for_messages(client, _) + } Error(Nil) -> // A message was not received, stop listening and return captured messages in order. diff --git a/test/guides/as-part-of-supervisor.md b/test/guides/as-part-of-supervisor.md index a49c7a1..1243490 100644 --- a/test/guides/as-part-of-supervisor.md +++ b/test/guides/as-part-of-supervisor.md @@ -13,7 +13,9 @@ Lets assume we need to have multiple "sessions" indexed on our system: ```gleam import chip import gleam/erlang/process -import gleam/otp/supervisor +import gleam/otp/actor +import gleam/otp/static_supervisor as supervisor +import gleam/otp/supervision pub fn main() { let self = process.new_subject() @@ -30,33 +32,19 @@ pub fn main() { // ------ Supervision Tree ------ // -// A context type will help carry around state between children in the supervisor. -type Context { - Context(caller: process.Subject(Registry), registry: Registry, group: Group) -} - // The tree is defined by calling a hierarchy of specifications fn supervisor(main: process.Subject(Registry)) { - supervisor.start_spec( - supervisor.Spec( - argument: main, - max_frequency: 5, - frequency_period: 1, - init: fn(children) { - children - // First spawn the registry. - |> supervisor.add(registry_spec()) - // Then spawn all sessions. - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - // Finally notify the main process we're ready. - |> supervisor.add(ready()) - }, - ), - ) + let registry = chip.start(chip.Named("sessions")) + let assert Ok(registry_subject) = registry + supervisor.new(supervisor.OneForOne) + |> supervisor.add(supervision.worker(fn() { registry })) + |> supervisor.add(session_spec(registry_subject.data, GroupA)) + |> supervisor.add(session_spec(registry_subject.data, GroupB)) + |> supervisor.add(session_spec(registry_subject.data, GroupC)) + |> supervisor.add(session_spec(registry_subject.data, GroupA)) + |> supervisor.add(session_spec(registry_subject.data, GroupB)) + |> supervisor.add(ready(main, registry_subject.data)) + |> supervisor.start() } // ------ Registry ------ // @@ -64,38 +52,23 @@ fn supervisor(main: process.Subject(Registry)) { type Registry = chip.Registry(Message, Group) -fn registry_spec() { - // The registry childspec first starts the registry. - supervisor.worker(fn(_caller: process.Subject(Registry)) { - chip.start(chip.Named("sessions")) - }) - // After starting we transform the parameter from caller into a context for - // the sessions we want to register. - |> supervisor.returning(fn(caller, registry) { - Context(caller, registry, GroupA) - }) -} - // ------ Session ------- // -fn session_spec() { - supervisor.worker(fn(context: Context) { - start_session(context.registry, context.group) - }) - |> supervisor.returning(fn(context: Context, _game_session) { - // Increments the id for the next session. - Context(..context, group: next_group(context.group)) - }) +fn session_spec(registry: Registry, group: Group) { + supervision.worker(fn() { start_session(registry, group) }) } -fn start_session( - with registry: Registry, - group group: Group, -) -> supervisor.StartResult(Message) { +fn start_session(with registry: Registry, group group: Group) { // Mock function to startup a new session. - let session = process.new_subject() - chip.register(registry, group, session) - Ok(session) + case actor.new([]) |> actor.start() { + Ok(session) -> { + chip.register(registry, group, session.data) + Ok(session) + } + Error(e) -> { + Error(e) + } + } } // ------ Helpers ------ // @@ -109,21 +82,22 @@ type Group { GroupC } -fn next_group(group) { - case group { - GroupA -> GroupB - GroupB -> GroupC - GroupC -> GroupA - } -} - -fn ready() { +fn ready(main: process.Subject(Registry), registry: Registry) { // This childspec is a noop addition to the supervisor, on return it // will send back the registry reference. - supervisor.worker(fn(_context: Context) { Ok(process.new_subject()) }) - |> supervisor.returning(fn(context: Context, _self) { - process.send(context.caller, context.registry) - Nil + supervision.worker(fn() { + actor.new_with_initialiser(10, fn(self) { + process.send(main, registry) + + let selector = + process.new_selector() + |> process.select(self) + + actor.initialised(Nil) + |> actor.selecting(selector) + |> Ok + }) + |> actor.start() }) } ``` diff --git a/test/guides/as-subject-index.md b/test/guides/as-subject-index.md index a7eae7c..f4428ec 100644 --- a/test/guides/as-subject-index.md +++ b/test/guides/as-subject-index.md @@ -9,8 +9,7 @@ This pattern would be a way of re-defining chip as an indexed store: ```gleam import chip import gleam/erlang/process -import gleam/otp/actor -import gleam/otp/supervisor +import gleam/otp/supervision pub type Store(message) = chip.Registry(message, Int) @@ -18,12 +17,12 @@ pub type Store(message) = pub type Id = Int -pub fn start() -> Result(Store(message), actor.StartError) { +pub fn start() { chip.start(chip.Unnamed) } pub fn childspec() { - supervisor.worker(fn(_index) { start() }) + supervision.worker(start) } pub fn index(store: Store(message), id: Id, subject: process.Subject(message)) { @@ -53,11 +52,11 @@ pub fn main() { let assert Ok(session_2) = game.start(DrawCard) let assert Ok(session_3) = game.start(DrawCard) - store.index(sessions, 1, session_1) - store.index(sessions, 2, session_2) - store.index(sessions, 3, session_3) + store.index(sessions.data, 1, session_1.data) + store.index(sessions.data, 2, session_2.data) + store.index(sessions.data, 3, session_3.data) - router(sessions, "/resource/", 2) + router(sessions.data, "/resource/", 2) } fn router(sessions, url, id) { diff --git a/test/guides/as_local_pubsub.gleam b/test/guides/as_local_pubsub.gleam index 9106780..5932701 100644 --- a/test/guides/as_local_pubsub.gleam +++ b/test/guides/as_local_pubsub.gleam @@ -3,7 +3,6 @@ import chip import gleam/erlang/process import gleam/int import gleam/list -import gleam/otp/task pub type Channel { General @@ -17,27 +16,28 @@ pub type Event { pub fn main() { let assert Ok(pubsub) = pubsub.start() + let pubsub_subject = pubsub.data // For this scenario, out of simplicity, all clients are the current process. let client = process.new_subject() // Client is interested in coffee and pets. - chip.register(pubsub, Coffee, client) - chip.register(pubsub, Pets, client) + chip.register(pubsub_subject, Coffee, client) + chip.register(pubsub_subject, Pets, client) // Lets assume this is the server process broadcasting a welcome message. - task.async(fn() { - chip.members(pubsub, General, 50) + process.spawn(fn() { + chip.members(pubsub_subject, General, 50) |> list.each(fn(client) { Event(id: 1, message: "Welcome to General! Follow rules and be nice.") |> process.send(client, _) }) - chip.members(pubsub, Coffee, 50) + chip.members(pubsub_subject, Coffee, 50) |> list.each(fn(client) { Event(id: 2, message: "Ice breaker! Favorite cup of coffee?") |> process.send(client, _) }) - chip.members(pubsub, Pets, 50) + chip.members(pubsub_subject, Pets, 50) |> list.each(fn(client) { Event(id: 3, message: "Pets!") |> process.send(client, _) @@ -57,7 +57,10 @@ pub fn main() { }) } -fn listen_for_messages(client, messages) -> List(String) { +fn listen_for_messages( + client: process.Subject(Event), + messages: List(String), +) -> List(String) { // This function will listen until messages stop arriving for 100 milliseconds. // A selector is useful to transform our Events into types a client expects, @@ -65,14 +68,15 @@ fn listen_for_messages(client, messages) -> List(String) { // events into strings with the `to_string` function. let selector = process.new_selector() - |> process.selecting(client, to_string) + |> process.select_map(client, to_string) - case process.select(selector, 100) { - Ok(message) -> + case process.selector_receive(selector, 100) { + Ok(message) -> { // A message was received, capture it and attempt to listen for another message. message |> list.prepend(messages, _) |> listen_for_messages(client, _) + } Error(Nil) -> // A message was not received, stop listening and return captured messages in order. diff --git a/test/guides/as_part_of_supervisor.gleam b/test/guides/as_part_of_supervisor.gleam index abc7976..f29b69f 100644 --- a/test/guides/as_part_of_supervisor.gleam +++ b/test/guides/as_part_of_supervisor.gleam @@ -1,6 +1,8 @@ import chip import gleam/erlang/process -import gleam/otp/supervisor +import gleam/otp/actor +import gleam/otp/static_supervisor as supervisor +import gleam/otp/supervision pub fn main() { let self = process.new_subject() @@ -17,33 +19,19 @@ pub fn main() { // ------ Supervision Tree ------ // -// A context type will help carry around state between children in the supervisor. -type Context { - Context(caller: process.Subject(Registry), registry: Registry, group: Group) -} - // The tree is defined by calling a hierarchy of specifications fn supervisor(main: process.Subject(Registry)) { - supervisor.start_spec( - supervisor.Spec( - argument: main, - max_frequency: 5, - frequency_period: 1, - init: fn(children) { - children - // First spawn the registry. - |> supervisor.add(registry_spec()) - // Then spawn all sessions. - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - |> supervisor.add(session_spec()) - // Finally notify the main process we're ready. - |> supervisor.add(ready()) - }, - ), - ) + let registry = chip.start(chip.Named("sessions")) + let assert Ok(registry_subject) = registry + supervisor.new(supervisor.OneForOne) + |> supervisor.add(supervision.worker(fn() { registry })) + |> supervisor.add(session_spec(registry_subject.data, GroupA)) + |> supervisor.add(session_spec(registry_subject.data, GroupB)) + |> supervisor.add(session_spec(registry_subject.data, GroupC)) + |> supervisor.add(session_spec(registry_subject.data, GroupA)) + |> supervisor.add(session_spec(registry_subject.data, GroupB)) + |> supervisor.add(ready(main, registry_subject.data)) + |> supervisor.start() } // ------ Registry ------ // @@ -51,38 +39,23 @@ fn supervisor(main: process.Subject(Registry)) { type Registry = chip.Registry(Message, Group) -fn registry_spec() { - // The registry childspec first starts the registry. - supervisor.worker(fn(_caller: process.Subject(Registry)) { - chip.start(chip.Named("sessions")) - }) - // After starting we transform the parameter from caller into a context for - // the sessions we want to register. - |> supervisor.returning(fn(caller, registry) { - Context(caller, registry, GroupA) - }) -} - // ------ Session ------- // -fn session_spec() { - supervisor.worker(fn(context: Context) { - start_session(context.registry, context.group) - }) - |> supervisor.returning(fn(context: Context, _game_session) { - // Increments the id for the next session. - Context(..context, group: next_group(context.group)) - }) +fn session_spec(registry: Registry, group: Group) { + supervision.worker(fn() { start_session(registry, group) }) } -fn start_session( - with registry: Registry, - group group: Group, -) -> supervisor.StartResult(Message) { +fn start_session(with registry: Registry, group group: Group) { // Mock function to startup a new session. - let session = process.new_subject() - chip.register(registry, group, session) - Ok(session) + case actor.new([]) |> actor.start() { + Ok(session) -> { + chip.register(registry, group, session.data) + Ok(session) + } + Error(e) -> { + Error(e) + } + } } // ------ Helpers ------ // @@ -96,20 +69,21 @@ type Group { GroupC } -fn next_group(group) { - case group { - GroupA -> GroupB - GroupB -> GroupC - GroupC -> GroupA - } -} - -fn ready() { +fn ready(main: process.Subject(Registry), registry: Registry) { // This childspec is a noop addition to the supervisor, on return it // will send back the registry reference. - supervisor.worker(fn(_context: Context) { Ok(process.new_subject()) }) - |> supervisor.returning(fn(context: Context, _self) { - process.send(context.caller, context.registry) - Nil + supervision.worker(fn() { + actor.new_with_initialiser(10, fn(self) { + process.send(main, registry) + + let selector = + process.new_selector() + |> process.select(self) + + actor.initialised(Nil) + |> actor.selecting(selector) + |> Ok + }) + |> actor.start() }) } diff --git a/test/guides/as_subject_index.gleam b/test/guides/as_subject_index.gleam index 948254a..92978d0 100644 --- a/test/guides/as_subject_index.gleam +++ b/test/guides/as_subject_index.gleam @@ -8,11 +8,11 @@ pub fn main() { let assert Ok(session_2) = game.start(DrawCard) let assert Ok(session_3) = game.start(DrawCard) - store.index(sessions, 1, session_1) - store.index(sessions, 2, session_2) - store.index(sessions, 3, session_3) + store.index(sessions.data, 1, session_1.data) + store.index(sessions.data, 2, session_2.data) + store.index(sessions.data, 3, session_3.data) - router(sessions, "/resource/", 2) + router(sessions.data, "/resource/", 2) } fn router(sessions, url, id) { diff --git a/test/guides/readme.gleam b/test/guides/readme.gleam index 42f5ba2..3eda729 100644 --- a/test/guides/readme.gleam +++ b/test/guides/readme.gleam @@ -14,10 +14,10 @@ pub fn main() { let assert Ok(session_b) = game.start(FireDice) let assert Ok(session_c) = game.start(PlayChip) - chip.register(registry, GroupA, session_a) - chip.register(registry, GroupB, session_b) - chip.register(registry, GroupA, session_c) + chip.register(registry.data, GroupA, session_a.data) + chip.register(registry.data, GroupB, session_b.data) + chip.register(registry.data, GroupA, session_c.data) - chip.members(registry, GroupA, 50) + chip.members(registry.data, GroupA, 50) |> list.each(fn(session) { game.next(session) }) }