module Lapis::Docs::D_CONCURRENCY_AND_FIBERS::B_OS_THREADS_AND_CHANNELS

Overview

OS Threads & Actor Channels

Comprehensive guide to multi-core parallel computation, procedural generation, pathfinding, and thread-safe communication using the Actor Pattern with Godot::Channel(T).

Executive Summary & Key Topics

Topic Method / Anchor Description
Thread Safety Rules .topic_00_threading_invariants SceneTree modification thread affinity, buffered channels, and Crystal 1.20+ rules.
The Actor Pattern with Channels .topic_01_actor_pattern Spawning background threads and communicating via immutable message channels.
Crystal 1.20+ Execution Context Safety .topic_02_crystal_120_context_safety Why unbuffered channels suspend and raise NilAssertionError on raw OS threads.

Related Guides & Source References

Defined in:

libgodot/docs/d_concurrency_and_fibers/b_os_threads_and_channels.cr

Class Method Summary

Class Method Detail

def self.topic_00_threading_invariants : Nil #

Thread Safety Rules: SceneTree modification thread affinity, buffered channels, and Crystal 1.20+ rules.

Key Topics & Information

  • Rule 1: NEVER mutate the SceneTree (add_child, queue_free) from a background thread
  • Rule 2: Always use buffered channels across OS threads (capacity >= 1)
  • Rule 3: Crystal collections (Hash, Array) must be protected with ::Thread::Mutex

def self.topic_01_actor_pattern : Nil #

The Actor Pattern with Channels: Spawning background threads and communicating via immutable message channels.

struct PathResult
  getter path : Array(Vector2)
  def initialize(@path : Array(Vector2)); end
end

node NavigationManager < Node do
  @channel = Godot::Channel(PathResult).new(capacity: 100)

  def request_path(start_pos : Vector2, end_pos : Vector2) : Void
    Thread.new do
      # Compute path on background thread:
      computed = compute_heavy_astar(start_pos, end_pos)
      @channel.send(PathResult.new(computed))
    end
  end

  def _process(delta : Float64) : Void
    # Drain channel non-blockingly on main thread:
    while result = @channel.receive?
      apply_path_to_player(result.path)
    end
  end
end

def self.topic_02_crystal_120_context_safety : Nil #

Crystal 120+ Execution Context Safety: Why unbuffered channels suspend and raise NilAssertionError on raw OS threads.

The Hazard in Crystal 1.20+:

In Crystal 1.20+, unbuffered channels (Channel(T).new) suspend the calling fiber when no receiver is ready. On raw OS threads (Thread.new), Fiber#execution_context is nil, so suspending raises: NilAssertionError: Fiber#execution_context cannot be nil

The Solution:

Godot::Channel(T) enforces capacity >= 1. Senders deposit messages into the ring buffer without suspending the calling thread!