Skip to content

feat(client): unify high-level client lifecycle - #48

Merged
RoboticHuman merged 8 commits into
masterfrom
improve-client-api
Oct 3, 2026
Merged

RoboticHuman merged 8 commits into
masterfrom
improve-client-api

Conversation

@RoboticHuman

Copy link
Copy Markdown
Owner
  • give every high-level client the same lifecycle: update() returns SyncUpdate, plus wait_until_ready() and submit_and_wait() that return False only on timeout and raise for rejected, parked, or closed states
  • deliver token, metadata, and playback callbacks during update() and write transactions from a background thread by default
  • reconnect UsdPublisher from update(); disconnect() pauses reconnection
  • report PARKED for stage-less clients and expose unsent work, deferred layer records, and edit-target scope in ClientStatus
  • add SharedStageClient.resume_recovery() and playback commands
  • seed the EventDispatcher cursor from receiver.sync_from so snapshot continuation no longer replays full history over the snapshot
  • add CHANGELOG.md, enforced by check_versions.py

BREAKING CHANGE: UsdReceiver.update() and UsdPublisher.update() return SyncUpdate instead of int. Transport callbacks run during update() unless callbacks_on_update=False. UsdPublisher.update() requires start(). publish_current_edit_target() keeps the snapshot while disconnected, and ManagedClient.rebind_stage() refuses unsent or unacknowledged work unless discard_unsent=True.

- give every high-level client the same lifecycle: update() returns
  SyncUpdate, plus wait_until_ready() and submit_and_wait() that return
  False only on timeout and raise for rejected, parked, or closed states
- deliver token, metadata, and playback callbacks during update() and
  write transactions from a background thread by default
- reconnect UsdPublisher from update(); disconnect() pauses reconnection
- report PARKED for stage-less clients and expose unsent work, deferred
  layer records, and edit-target scope in ClientStatus
- add SharedStageClient.resume_recovery() and playback commands
- seed the EventDispatcher cursor from receiver.sync_from so snapshot
  continuation no longer replays full history over the snapshot
- add CHANGELOG.md, enforced by check_versions.py

BREAKING CHANGE: UsdReceiver.update() and UsdPublisher.update() return
SyncUpdate instead of int. Transport callbacks run during update() unless
callbacks_on_update=False. UsdPublisher.update() requires start().
publish_current_edit_target() keeps the snapshot while disconnected, and
ManagedClient.rebind_stage() refuses unsent or unacknowledged work unless
discard_unsent=True. See CHANGELOG.md for migration notes.
- state the lifecycle, error model, and host loop once in
  usd-native-integration.md, with the exceptions as a table
- shorten the resume_recovery section and example READMEs
- collapse the repeated wait_until_ready/submit_and_wait docstrings
  to one line; the raising rules live in raise_if_blocked
- drop docstring paragraphs that restated the class docstring or docs
- replace the nine on_* callback arguments with one observer=ClientObserver;
  every method runs inside update() with typed payloads (AppliedBatch,
  StageMetadata, PlaybackState, PlaybackClaim)
- add update(max_messages=) to ManagedClient and SharedStageClient so a
  reconnect backlog spreads over frames; local edits wait for it
- resolve sender credentials once per connect attempt (token_provider)
  instead of reading the token file on every update() while reconnecting
- default background_send to False: the Python writer waits for the GIL
  and adds about 6 ms per write in Python-busy hosts
- add ClientStatus.can_author; connect() and flush() default to 10 s
- log unreachable-server retries on one line instead of a traceback
- install the Blender add-on before importing it in the reconnect test;
  the loaded native module made reinstalls abort halfway on Windows
- trim duplicated tests, docstrings, and docs

BREAKING CHANGE: high-level clients take observer= instead of on_* callback
arguments; callbacks_on_update and UsdReceiver.applying_seq are removed.
client.stage_metadata returns StageMetadata. connect() and flush() default
to a 10 second timeout. See CHANGELOG.md for migration notes.
- remove client properties that duplicated ClientStatus fields
  (connection, sync, rejection, event counts, and recovery summaries)
- add ClientStatus.auth_rejected to distinguish authentication from
  protocol rejection
- read status in the blocking helpers, MCP server, usdview, and docs;
  keep private fast checks inside update()
- restructure the 0.5.0 changelog entry around a Migrating from 0.4
  section and correct entries that listed removed properties

BREAKING CHANGE: read client.status instead of the removed properties;
the event counts are status.pending_events, prepared_events,
deferred_events, and acknowledged_events_total. CHANGELOG.md lists every
replacement under (Migrating from 0.4).
- Drop duplicate assertions and a cross-client test that repeated
  per-client coverage; rename tests to the behavior they check.
- Make RecordingObserver a plain ClientObserver subclass.
- Docs: clarify flush and publish_current_edit_target, note that a
  ManagedClient stops receiving while its edit target is foreign, and
  drive recovery UI from can_author.
- Changelog: list EventDispatcher.backlog_pending and
  NoticeEmitter.has_local_changes.
ManagedClient, SharedStageClient, UsdReceiver, and UsdPublisher build on
_client_base.py. ClientBase owns lifecycle, status, observer hooks, and
the credential; PublishingClientBase adds recovery, playback, and
durable completion; EmitterClientBase adds emitter capture and transform
coalescing. Each client keeps only its role-specific wiring and status
hooks, and every phase comes from one compute_phase precedence.

- ClientCredential holds the one token both roles present. EventSender
  and ReceiverThread take token_provider= and read it before each
  connection attempt.
- Observer wiring moves to _observer_hooks.py as typed ObserverHooks;
  client_observer.py keeps only the public contract.
- Rename ClientStatus.edit_target_is_shared to edit_target_is_published.
- Remove UsdReceiver.layered_replay_active and client-side negotiation
  checks; the receiver already rejects a handshake without the
  requested mode.
- ManagedClient.flush() raises on a rejected connection, and
  UsdReceiver reports CONNECTING while reconnecting, matching the
  other clients.
- Tests: ClientCredential, phase precedence, lifecycle conformance
  across all four clients, and a force_handshake helper.
- A max_messages budget no longer holds local edits forever under
  sustained inbound traffic. BacklogHold counts the messages queued
  before a batch was frozen; EventDispatcher.drained_message_count
  replaces backlog_pending.
- NoticeEmitter.has_local_changes no longer stays True after a removed
  local definition reveals a weaker prim.
- close() delivers notifications still queued, so a token issued by the
  last handshake reaches host-owned storage.
- close() called from an observer while events apply takes effect once
  the apply returns.
- wait_until_ready() and submit_and_wait() raise ConnectionError when
  nothing will reconnect (a disconnected UsdPublisher, or a receiver with
  reconnect=False that lost its connection) instead of waiting forever;
  ReceiverThread.stopped reports the exited thread.
- SharedStageClient.repair_and_resume() keeps the recovery state when
  repair fails validation.
- Stage edits made in on_resync, or in on_applied during
  refresh_asset_dependency(), are not published.
- ClientCredential serializes token reads and issuance; the sender rea
  its token only for an attempt that has time left.
- SharedStageClient.status reads deferred layer keys kept current as
  records defer instead of rebuilding them per call.
- update() is declared on ClientBase and accepts max_messages on every
  client; repair_and_resume() moves to EmitterClientBase.
- Docs and changelog: observer threading contract, typed observer
  payloads in the migration notes, flush and OFFLINE behavior,
  can_author in examples, submit_and_wait at shutdown.
- Tests: regression tests for each fix, budget tests through real
  drains, and 15 redundant cases removed.
With background_send, disconnect() closed the native connection of a
handshake that had not published its socket yet. The resumed handshake
then failed accept_hello with INVALID_PHASE and quarantined the producer
session, so a later reconnect could not publish. The socket generation is
now recorded at publication, and _close() only ends the published
socket's connection.
@RoboticHuman
RoboticHuman merged commit 2b415e7 into master Oct 3, 2026
1 check passed
@RoboticHuman
RoboticHuman deleted the improve-client-api branch October 3, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant