Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a177c2e
feat: implement lazy-deploy on the system network generation
doudou Nov 12, 2025
c7b5675
chore: extract the adapt-runtime-plan part of the algorithm out of En…
doudou Dec 4, 2025
f94f662
chore: documentation
doudou Dec 4, 2025
3d54e95
fix: use orocos_name only in MergeSolver#may_merge_task_contexts?
doudou Dec 4, 2025
3f01bb2
fix: make sure the tasks of stubbed deployment models have names
doudou Dec 4, 2025
6194924
feat: modify runtime network adaptation to handle the lazy deployment…
doudou Dec 4, 2025
a2feccd
fix: propagate the lazy_deploy flag when validating deployments from …
doudou Dec 4, 2025
1e95ca2
fix: use orocos_name instead of execution_agent to find deployed task…
doudou Dec 4, 2025
58984d5
fix: display of the TEST_LOG_LEVEL
doudou Dec 4, 2025
c57c1e1
fix: make the syskit configuration precedence relation strong
doudou Dec 4, 2025
c0cb782
chore: add syskit-netgen: prefix to some timepoints that were missing it
doudou Dec 4, 2025
dd27a1e
fix: clear up relations only on finished tasks
doudou Dec 4, 2025
2f8f5d9
chore: fix complexity cops in RuntimeNetworkAdaptation
doudou Dec 5, 2025
696decd
fix: create well-named predicates instead of testing for orocos_name …
doudou Dec 10, 2025
de73964
fix: duplicate deployments in the non-lazy but early deployment case
doudou Dec 8, 2025
50b9c98
fix: update a task's orogen_model when we select it even in lazy mode
doudou Dec 17, 2025
a3e38d4
fix: instanciate scheduler tasks ("master tasks") during lazy deploy
doudou Dec 17, 2025
48c32fd
chore: disable Style/InverseMethod for a class ordering test
doudou Dec 18, 2025
98765ee
fix: separate initial information gathering from resolution of trigge…
doudou Dec 19, 2025
5bf679d
fix: duplicate scheduler tasks appearing after lazy network adaptatio…
doudou Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/syskit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module ProcessManagers
require "syskit/models/task_context"
require "syskit/models/ruby_task_context"
require "syskit/models/deployment"
require "syskit/models/deployed_task_instanciation"
require "syskit/models/configured_deployment"
require "syskit/models/deployment_group"

Expand Down
2 changes: 1 addition & 1 deletion lib/syskit/component.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Syskit
Roby::EventStructure.relation "SyskitConfigurationPrecedence"
Roby::EventStructure.relation "SyskitConfigurationPrecedence", strong: true

# Base class for models that represent components (TaskContext,
# Composition)
Expand Down
128 changes: 40 additions & 88 deletions lib/syskit/deployment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,58 +97,29 @@ def has_orocos_name?(orocos_name)
end

def instanciate_all_tasks
model.each_orogen_deployed_task_context_model.map do |act|
task(name_mappings[act.name])
each_orogen_deployed_task_context_model.map do |act|
task(act.name)
end
end

# The list of deployed task contexts for this particular deployment
#
# It takes into account deployment prefix
def each_orogen_deployed_task_context_model(&block)
model.each_orogen_deployed_task_context_model(&block)
orogen_model.task_activities.each(&block)
end

# Either find the existing task that matches the given deployment specification,
# or creates and adds it.
#
# @param (see #task)
def find_or_create_task(name, syskit_task_model = nil, auto_conf: false)
orogen_task_deployment_model = deployed_orogen_model_by_name(name)
if orogen_master = orogen_task_deployment_model.master
mapped_master = name_mappings[orogen_master.name]
scheduler_task = find_or_create_task(
mapped_master, auto_conf: true
)
candidates = scheduler_task.each_parent_task
else
candidates = each_executed_task
end
# The deployment's orogen model with the name mappings applied
def orogen_model
return @orogen_model if @orogen_model

# I don't know why name_mappings[orogen.name] would not be
# equal to 'name' and I couldn't find a reason for this in the
# git history when I refactored this.
#
# I keep it here for now, just in case, but that would need to
# be investigated
#
# TODO
mapped_name = name_mappings[orogen_task_deployment_model.name]
candidates.each do |task|
return task if task.orocos_name == mapped_name
end

create_deployed_task(
orogen_task_deployment_model,
syskit_task_model,
scheduler_task, auto_conf: auto_conf
)
@orogen_model = model.map_orogen_model(name_mappings)
end

def deployed_orogen_model_by_name(name)
orogen_task_deployment =
each_orogen_deployed_task_context_model
.find { |act| name == name_mappings[act.name] }
.find { |act| name == act.name }
unless orogen_task_deployment
available = each_orogen_deployed_task_context_model
.map { |act| name_mappings[act.name] }
Expand All @@ -168,8 +139,7 @@ def deployed_orogen_model_by_name(name)
# Create and add a task model supported by this deployment
#
# @param [OroGen::Spec::TaskDeployment] orogen_task_deployment_model
# the orogen model that describes this
# deployment
# the orogen model that describes this deployment, already mapped
# @param [Models::TaskContext,nil] syskit_task_model the expected
# syskit task model, or nil if it is meant to use the basic model.
# This is useful in specialized models (e.g. dynamic services)
Expand All @@ -180,17 +150,11 @@ def deployed_orogen_model_by_name(name)
# a configuration that matches the task's orocos name (if it exists). This
# is mostly used for scheduling tasks, which are automatically instanciated
# by Syskit.
#
# @see find_or_create_task task
def create_deployed_task(
orogen_task_deployment_model,
syskit_task_model, scheduler_task, auto_conf: false
)
mapped_name = name_mappings[orogen_task_deployment_model.name]
def create_deployed_task(orogen_task_deployment_model, syskit_task_model)
mapped_name = orogen_task_deployment_model.name
if ready? && !(remote_handles = remote_task_handles[mapped_name])
raise InternalError,
"no remote handle describing #{mapped_name} in #{self}" \
"(got #{remote_task_handles.keys.sort.join(', ')})"
"cannot find remote task handles for #{mapped_name}"
end

if task_context_in_fatal?(mapped_name)
Expand All @@ -199,29 +163,15 @@ def create_deployed_task(
"from #{self}"
end

base_syskit_task_model = model.resolve_syskit_model_for_deployed_task(
orogen_task_deployment_model
task = instanciate_deployed_task(
mapped_name,
orogen_model: orogen_task_deployment_model,
syskit_model: syskit_task_model,
plan: plan
)
syskit_task_model ||= base_syskit_task_model
unless syskit_task_model <= base_syskit_task_model
raise ArgumentError,
"incompatible explicit selection of task model " \
"#{syskit_task_model} for the model of #{mapped_name} in #{self}, " \
"expected #{base_syskit_task_model} or one of its subclasses"
end

task = syskit_task_model
.new(orocos_name: mapped_name, read_only: read_only?(mapped_name))
plan.add(task)
task.executed_by self
if scheduler_task
task.depends_on scheduler_task, role: "scheduler"
task.should_configure_after scheduler_task.start_event
end

task.orogen_model = orogen_task_deployment_model
task.initialize_remote_handles(remote_handles) if remote_handles
auto_select_conf(task) if auto_conf
task
end

Expand All @@ -237,40 +187,42 @@ def read_only?(mapped_name)
# model that should be used to create the task, if it is not the
# same as the base model. This is used for specialized models (e.g.
# dynamic services)
def task(name, syskit_task_model = nil)
def task(name, syskit_task_model = nil, setup_scheduler: true)
if finishing? || finished?
raise InvalidState,
"#{self} is either finishing or already " \
"finished, you cannot call #task"
end

orogen_task_deployment_model = deployed_orogen_model_by_name(name)
task = create_deployed_task(orogen_task_deployment_model, syskit_task_model)
if setup_scheduler
task_setup_scheduler(task, existing_tasks: executed_tasks_by_name)
end
task
end

if (orogen_master = orogen_task_deployment_model.master)
scheduler_task = find_or_create_task(
orogen_master.name, auto_conf: true
# (see DeployedTaskInstanciation#task_setup_scheduler)
def task_setup_scheduler(task, existing_tasks: {})
return unless (scheduler_task = super)

scheduler_task.executed_by self

if ready? && !scheduler_task.has_remote_information?
scheduler_task.initialize_remote_handles(
remote_task_handles.fetch(task.orocos_name)
)
end
create_deployed_task(
orogen_task_deployment_model,
syskit_task_model, scheduler_task
)
scheduler_task
end

# Selects the configuration of a master task
include Models::DeployedTaskInstanciation

# The tasks executed by this deployment, as a name to task hash
#
# Master tasks are auto-injected in the network, and as such the
# user cannot select their configuration. This picks either
# ['default', task.orocos_name] if the master task's has a configuration
# section matching the task's name, or ['default'] otherwise.
private def auto_select_conf(task)
manager = task.model.configuration_manager
task.conf =
if manager.has_section?(task.orocos_name)
["default", task.orocos_name]
else
["default"]
end
# @return [Hash]
def executed_tasks_by_name
each_executed_task.to_h { |t| [t.orocos_name, t] }
end

##
Expand Down
24 changes: 9 additions & 15 deletions lib/syskit/models/configured_deployment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ def non_logger_task_names
@name_mappings.values.grep_v(/_Logger$/)
end

def read_only?(orocos_name)
read_only.include?(orocos_name)
end

include DeployedTaskInstanciation

# @api private
#
# Resolves the read_only argument into an Array of task names to be set as
Expand Down Expand Up @@ -102,13 +108,7 @@ def command_line(loader: Roby.app.default_pkgconfig_loader, **options)
def orogen_model
return @orogen_model if @orogen_model

@orogen_model = model.orogen_model.dup
orogen_model.task_activities.map! do |activity|
activity = activity.dup
activity.name = name_mappings[activity.name] || activity.name
activity
end
@orogen_model
@orogen_model = model.map_orogen_model(name_mappings)
end

# Enumerate the tasks that are deployed by this configured
Expand All @@ -131,14 +131,8 @@ def each_deployed_task_model
# Enumerate the oroGen specification for the deployed tasks
#
# @yieldparam [OroGen::Spec::TaskDeployment]
def each_orogen_deployed_task_context_model
return enum_for(__method__) unless block_given?

model.each_orogen_deployed_task_context_model do |deployed_task|
task = deployed_task.dup
task.name = name_mappings[task.name] || task.name
yield(task)
end
def each_orogen_deployed_task_context_model(&block)
orogen_model.task_activities.each(&block)
end

# Create a new deployment task that can represent self in a plan
Expand Down
116 changes: 116 additions & 0 deletions lib/syskit/models/deployed_task_instanciation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# frozen_string_literal: true

module Syskit
module Models
# Features that implement the instanciation of deployed tasks, compatible with
# both {ConfiguredDeployment} and {Syskit::Deployment}
module DeployedTaskInstanciation
# Create a task from this deployment's
#
# @param [String] orocos_name the mapped name of the deployed task
# @param [OroGen::Spec::DeployedTask,nil] orogen_model the orogen model of the
# deployed task, if known. Pass nil if it has to be resolved.
# @param [TaskContext,nil] syskit_model the syskit model that should be used
# to create the task instance. It may be a submodel of the deployment's task
# model. Pass nil to resolve from the deployment model
# @param [Roby::Plan,nil] plan the plan in which the task should be created
# @return [(OroGen::Spec::DeployedTask,TaskContext)] the resolved orogen
# models and
def instanciate_deployed_task(
orocos_name, orogen_model: nil, syskit_model: nil, plan: nil
)
orogen_model, syskit_model =
instanciate_deployed_task_resolve_task_model(
orocos_name, orogen_model, syskit_model
)

args = {
orogen_model: orogen_model,
orocos_name: orocos_name,
read_only: read_only?(orocos_name)
}
args[:plan] = plan if plan
syskit_model.new(**args)
end

# Create the 'scheduler' task of a task (if needed)
#
# OroGen components can be explicitly triggered by another component. This
# shows up as having a 'master' in the deployed task model. This method makes
# sure that the scheduler component is instanciated, and sets up proper
# relationship between the scheduled task and the scheduler task
def task_setup_scheduler(task, existing_tasks: {})
return unless (orogen_master_m = task.orogen_model.master)

mapped_master_name = orogen_master_m.name
unless (scheduler_task = existing_tasks[mapped_master_name])
scheduler_task =
instanciate_deployed_task(mapped_master_name, plan: task.plan)
scheduler_task.select_conf_from_name

existing_tasks =
existing_tasks.merge({ mapped_master_name => scheduler_task })
end

task_setup_scheduler(scheduler_task, existing_tasks: existing_tasks)

task.depends_on scheduler_task, role: "scheduler"
task.should_configure_after scheduler_task.start_event
scheduler_task
end

# Helper for {#instanciate_deployed_task} to resolve the syskit task model
# that should be used to represent the deployed task
#
# @param [String] orocos_name the mapped name of the deployed task
# @param [OroGen::Spec::DeployedTask,nil] orogen_model the orogen model of the
# deployed task, if known. Pass nil if it has to be resolved.
# @param [TaskContext,nil] syskit_model the syskit model that should be used
# to create the task instance. It may be a submodel of the deployment's task
# model. Pass nil to resolve from the deployment model
# @return [(OroGen::Spec::DeployedTask,TaskContext)] the resolved orogen
# models and
def instanciate_deployed_task_resolve_task_model(
orocos_name, orogen_model = nil, syskit_model = nil
)
orogen_model ||=
each_orogen_deployed_task_context_model
.find { |m| m.name == orocos_name }

unless orogen_model
raise ArgumentError, "no deployed task found for #{orocos_name}"
end

base_syskit_model = resolve_syskit_model_for_deployed_task(orogen_model)
if syskit_model && !(syskit_model <= base_syskit_model) # rubocop:disable Style/InverseMethods
raise ArgumentError,
"incompatible explicit selection of task model " \
"#{syskit_model} for the model of #{orogen_model} in " \
"#{self}, expected #{base_syskit_model} " \
"or one of its subclasses"
end

[orogen_model, syskit_model || base_syskit_model]
end

# Resolve the syskit task context model that should be used to represent a
# deployed task
#
# @param [OroGen::Spec::DeployedTask] orogen_deployed_task the model, which
# name has been mapped
def resolve_syskit_model_for_deployed_task(orogen_deployed_task)
unmapped_name = name_mappings.rassoc(orogen_deployed_task.name)&.first
unless unmapped_name
raise ArgumentError,
"no mapping points to name #{orogen_deployed_task.name}, " \
"known names: #{name_mappings}. This method expects the " \
"mapped name as argument"
end

model.resolve_syskit_model_for_deployed_task(
orogen_deployed_task, name: unmapped_name
)
end
end
end
end
Loading