Skip to content

Commit cc2ee5b

Browse files
committed
Merge branch 'main' into claude/interthread-python-wrapper-r75m1y
2 parents d1671cc + 999b323 commit cc2ee5b

6 files changed

Lines changed: 75 additions & 31 deletions

File tree

‎README.md‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ goby_launch -x julia_publisher_subscriber.launch
152152

153153
This launches `gobyd`, the Julia subscriber, and the Julia publisher, each in their own XTerm window. When finished, type CTRL-C into the original terminal (the one from which `goby_launch` was run).
154154

155-
The publisher sends a `NavigationReport` at 10 Hz on the `groups::julia_nav` group, which the subscriber prints as it receives them. Note that the Julia code identifies a group by its string value: `GOBY_DEFINE_GROUP` in `src/messages/groups.h` sets that string to the fully-qualified C++ name, so the group named in `interface.yml` and the string passed to `Goby.publish()`/`Goby.subscribe()` are the same text.
155+
The publisher sends a `NavigationReport` at 10 Hz on the `groups::julia_nav` group, which the subscriber prints as it receives them. The group is named through the module `goby_add_julia_app()` generates from `interface.yml` beside each application (`basic_julia_publisher_goby.jl`), so `groups.julia_nav` and `interprocess()` stand in for the group string and the layer constant. The expression then lives in one place, and a mistyped group is an `UndefVarError` naming it rather than a `GOBY_JULIA_FAIL` that terminates the application once the publish is reached.
156156

157157
Unlike the C++ applications, the Julia applications take the path to their configuration file as their only argument, so `basic_julia_publisher.pb.cfg` and `basic_julia_subscriber.pb.cfg` are provided alongside the launch file:
158158

@@ -161,6 +161,23 @@ basic_julia_publisher <config.pb.cfg>
161161
basic_julia_subscriber <config.pb.cfg>
162162
```
163163

164+
### Multi-Threaded Publish/Subscribe in Julia (ZeroMQ)
165+
166+
`src/interprocess/zeromq/julia/basic_multithread` is the Julia equivalent of the interthread publisher/subscriber, and shows the layers available to a Julia task.
167+
168+
`Goby.run(app, Main, [PublisherTask, SubscriberTask])` runs each task module on its own Julia thread. The publisher task sends `NavigationReport`s over `INTERTHREAD`, which is implemented in Julia over `Channel`s rather than in the generated C++ -- so its groups are plain strings chosen by the application rather than groups declared in `interface.yml`, and its messages can be any Julia value (the depth alert `Main` receives is a `NamedTuple`).
169+
170+
The subscriber task publishes the deep reports out to `INTERPROCESS` itself. Goby.jl hands those to the task that owns the C++ application, the way a C++ thread's `InterProcessForwarder` does, so no task needs a portal of its own and none has to route through `Main`. Subscribing works the same way: the publisher task subscribes to the same group and sees each report that goes out, alongside the separate `basic_julia_subscriber` process.
171+
172+
Julia fixes its thread count at startup, so the application needs one thread per task module plus three, for `Main`, the loop timer and the C++ application. `goby_add_julia_app()`'s `THREADS` gives that to the generated launcher.
173+
174+
To run:
175+
176+
```
177+
cd launch/interprocess/zeromq
178+
goby_launch -x julia_multithread.launch
179+
```
180+
164181
### GPS Driver
165182
A working example using a standard NMEA-0183 GPS is given in `src/interprocess/zeromq/gps_driver`.
166183

‎launch/interprocess/zeromq/julia_multithread.launch‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
gobyd
33
# the launcher starts julia with the thread count declared by THREADS in CMakeLists.txt
44
basic_julia_multithread basic_julia_multithread.pb.cfg
5-
# the reports the Julia application forwards to interprocess, seen from outside it
5+
# the deep reports the Julia application's subscriber task publishes, seen from outside it
66
basic_julia_subscriber basic_julia_subscriber.pb.cfg

‎src/interprocess/zeromq/julia/basic_multithread/interface.yml‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
# Only the interprocess publication is declared. INTERTHREAD is implemented in Julia, using
2-
# channels between tasks, and never reaches the generated C++ -- so an interthread group is a
3-
# plain string chosen by the application rather than a C++ group named here.
1+
# Only the interprocess traffic is declared. INTERTHREAD is implemented in Julia, using channels
2+
# between tasks, and never reaches the generated C++ -- so an interthread group is a plain string
3+
# chosen by the application rather than a C++ group named here.
4+
#
5+
# The application both publishes and subscribes to groups::julia_nav: the subscriber task sends
6+
# deep reports out, and the publisher task sees each one that goes.
47
application:
58
name: BasicJuliaMultithread
69
cpp_type: goby.zeromq.SingleThreadApplication
@@ -13,3 +16,7 @@ interprocess:
1316
- group: groups::julia_nav
1417
scheme: PROTOBUF
1518
type: goby3_examples::protobuf::NavigationReport
19+
subscribes:
20+
- group: groups::julia_nav
21+
scheme: PROTOBUF
22+
type: goby3_examples::protobuf::NavigationReport

‎src/interprocess/zeromq/julia/basic_multithread/multithread.jl‎

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
# Julia equivalent of src/interthread/basic_publisher_subscriber, showing the INTERTHREAD layer.
1+
# Julia equivalent of src/interthread/basic_publisher_subscriber, showing the INTERTHREAD layer
2+
# and pub/sub from a task rather than from Main.
23
#
3-
# A publisher task produces NavigationReports, a subscriber task consumes them and raises an
4-
# alert when the vehicle is deep, and Main forwards each report on to INTERPROCESS so the rest
5-
# of the Goby system sees it.
4+
# A publisher task produces NavigationReports on INTERTHREAD; a subscriber task consumes them,
5+
# and publishes the deep ones straight out to INTERPROCESS. Those reach every application on the
6+
# system that subscribed to the group (basic_julia_subscriber, in julia_multithread.launch), this
7+
# one included: the publisher task subscribed to the same group and sees each one.
68
#
7-
# Three things this example exists to show:
9+
# Four things this example exists to show:
810
#
911
# 1. INTERTHREAD is pure Julia. Goby.jl carries these messages between tasks over Channels,
1012
# so they never enter the generated C++ and interthread groups are not declared in
1113
# interface.yml -- any string will do.
1214
# 2. Because of that, an interthread message can be any Julia value, not only a protobuf
13-
# message. The alert below is a NamedTuple.
14-
# 3. INTERPROCESS is only available from Main, so a task that needs the outside world hands
15-
# the message to Main over INTERTHREAD, as the subscriber task does here. Publishing to it
16-
# from a task instead raises an AssertionError naming the task, which stops the
17-
# application.
15+
# message. The alert Main receives below is a NamedTuple.
16+
# 3. INTERPROCESS works from any task. Goby.jl hands the message to the task that owns the C++
17+
# application, the way a C++ thread's InterProcessForwarder does, so a task neither needs a
18+
# portal of its own nor has to route through Main.
19+
# 4. Subscribing from a task works the same way: the subscription is registered on the task
20+
# that owns the application, and messages come back to the task that asked for them.
1821
#
1922
# Julia fixes its thread count at startup, and Goby.run() needs one thread per task module plus
2023
# three (Main, the loop timer, and the C++ application). Two task modules therefore need five,
@@ -26,12 +29,17 @@ include(joinpath(@__DIR__, "..", "goby_application_init.jl"))
2629
init_cxxwrap("basic_julia_multithread")
2730
include_protos(@__MODULE__, "goby3_examples")
2831

32+
# the groups and layer accessors generated from interface.yml, written beside this script by
33+
# goby_add_julia_app()
34+
include(joinpath(@__DIR__, "basic_julia_multithread_goby.jl"))
35+
using .BasicJuliaMultithreadGoby: groups, interprocess
36+
2937
# interthread groups are Julia-side only, so they are named here rather than in interface.yml
3038
const NAV_GROUP = "nav"
3139
const DEPTH_ALERT_GROUP = "depth_alert"
3240

3341
# ---------------------------------------------------------------------------
34-
# Publisher task: runs on its own thread, publishing at the rate in goby_cfg.
42+
# Publisher task: produces reports, and watches the ones that reach interprocess.
3543
# ---------------------------------------------------------------------------
3644
module PublisherTask
3745

@@ -48,6 +56,15 @@ function loop()
4856
Goby.publish(Main.app, Goby.INTERTHREAD, Main.NAV_GROUP, nav)
4957
end
5058

59+
function published_nav(nav::Main.goby3_examples.protobuf.NavigationReport)
60+
println("[publisher] on the wire: z=$(round(nav.z, digits = 1))")
61+
end
62+
63+
# subscriptions belong in start(), which Goby.run() calls on this task's own thread
64+
function start()
65+
Goby.subscribe(Main.app, Main.interprocess(), Main.groups.julia_nav, published_nav)
66+
end
67+
5168
# read by Goby.run() to drive loop() from the timer thread
5269
goby_cfg = Dict(
5370
:loop_frequency => 1, # Hz
@@ -57,7 +74,7 @@ goby_cfg = Dict(
5774
end # module PublisherTask
5875

5976
# ---------------------------------------------------------------------------
60-
# Subscriber task: consumes reports and reports deep ones back to Main.
77+
# Subscriber task: consumes reports, and sends the deep ones to the outside.
6178
# ---------------------------------------------------------------------------
6279
module SubscriberTask
6380

@@ -67,38 +84,35 @@ function incoming_nav(nav::Main.goby3_examples.protobuf.NavigationReport)
6784
println("[subscriber] Rx: z=$(round(nav.z, digits = 1))")
6885

6986
if -nav.z > Main.alert_depth
87+
# straight out of this task: no portal here, and no hop through Main
88+
Goby.publish(Main.app, Main.interprocess(), Main.groups.julia_nav, nav)
89+
7090
# an interthread message is any Julia value; this one never becomes protobuf
7191
Goby.publish(Main.app, Goby.INTERTHREAD, Main.DEPTH_ALERT_GROUP,
7292
(depth = -nav.z, x = nav.x, y = nav.y))
7393
end
7494
end
7595

76-
# subscriptions belong in start(), which Goby.run() calls on this task's own thread
7796
function start()
7897
Goby.subscribe(Main.app, Goby.INTERTHREAD, Main.NAV_GROUP, incoming_nav)
7998
end
8099

81100
end # module SubscriberTask
82101

83102
# ---------------------------------------------------------------------------
84-
# Main: bridges the tasks to the rest of the Goby system.
103+
# Main: reports the alerts the subscriber task raises.
85104
# ---------------------------------------------------------------------------
86105

87-
function forward_nav(nav::goby3_examples.protobuf.NavigationReport)
88-
Goby.publish(app, Goby.INTERPROCESS, "groups::julia_nav", nav)
89-
end
90-
91106
function incoming_depth_alert(alert)
92107
println("[main] alert: $(round(alert.depth, digits = 1)) m at " *
93108
"x=$(round(alert.x, digits = 1)) y=$(round(alert.y, digits = 1))")
94109
end
95110

96111
function start()
97-
Goby.subscribe(app, Goby.INTERTHREAD, NAV_GROUP, forward_nav)
98112
Goby.subscribe(app, Goby.INTERTHREAD, DEPTH_ALERT_GROUP, incoming_depth_alert)
99113
end
100114

101-
# how often the C++ application drains the channel Main publishes to it through
115+
# how often the C++ application drains the channels the tasks publish to it through
102116
goby_cfg = Dict(:cxx_channel_check_frequency => 10)
103117

104118
config_str = Goby.read_cli_cfg()

‎src/interprocess/zeromq/julia/basic_publisher/publisher.jl‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ include(joinpath(@__DIR__, "..", "goby_application_init.jl"))
99
init_cxxwrap("basic_julia_publisher")
1010
include_protos(@__MODULE__, "goby3_examples")
1111

12+
# the groups and layer accessors generated from interface.yml, written beside this script by
13+
# goby_add_julia_app()
14+
include(joinpath(@__DIR__, "basic_julia_publisher_goby.jl"))
15+
using .BasicJuliaPublisherGoby: groups, interprocess
16+
1217
# ---------------------------------------------------------------------------
1318
# Publisher loop – called at the frequency specified in goby_cfg below.
1419
# ---------------------------------------------------------------------------
@@ -20,9 +25,7 @@ function loop()
2025
)
2126

2227
println("Tx: x=$(nav.x) y=$(nav.y) z=$(nav.z)")
23-
# the group is given by its string value, which GOBY_DEFINE_GROUP sets to
24-
# the fully-qualified C++ name (see src/messages/groups.h)
25-
Goby.publish(Main.app, Goby.INTERPROCESS, "groups::julia_nav", nav)
28+
Goby.publish(Main.app, interprocess(), groups.julia_nav, nav)
2629
end
2730

2831
config_str = Goby.read_cli_cfg()

‎src/interprocess/zeromq/julia/basic_subscriber/subscriber.jl‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ include(joinpath(@__DIR__, "..", "goby_application_init.jl"))
1010
init_cxxwrap("basic_julia_subscriber")
1111
include_protos(@__MODULE__, "goby3_examples")
1212

13+
# the groups and layer accessors generated from interface.yml, written beside this script by
14+
# goby_add_julia_app()
15+
include(joinpath(@__DIR__, "basic_julia_subscriber_goby.jl"))
16+
using .BasicJuliaSubscriberGoby: groups, interprocess
17+
1318
# ---------------------------------------------------------------------------
1419
# Called by Goby for each NavigationReport received. The message type is
1520
# inferred by Goby.subscribe() from this argument type.
@@ -20,9 +25,7 @@ end
2025

2126
# start() is called by Goby.run() before entering the event loop.
2227
function start()
23-
# the group is given by its string value, which GOBY_DEFINE_GROUP sets to
24-
# the fully-qualified C++ name (see src/messages/groups.h)
25-
Goby.subscribe(Main.app, Goby.INTERPROCESS, "groups::julia_nav", receive_incoming_msg)
28+
Goby.subscribe(Main.app, interprocess(), groups.julia_nav, receive_incoming_msg)
2629
end
2730

2831
config_str = Goby.read_cli_cfg()

0 commit comments

Comments
 (0)