Async tasks and ownership

Bounded background work, cancellation и server-owned применение state changes.

В этом разделе

Другие страницы раздела:

Правила

Pulse core сам владеет background work. Плагины могут запрашивать долгие операции, но не должны запускать goroutine, которые напрямую меняют player, world, entity, inventory, session, storage или runtime state.

  • Быстрые handle-методы могут выглядеть синхронно, но mutation всё равно проходит через owning service.
  • Долгая работа идет через bounded core task runners с context cancellation и queue backpressure.
  • Background job готовит данные; финальное изменение state применяет server-owned service.
  • Rejected work возвращает typed error, например task.ErrQueueFull или task.ErrClosed.
  • Plugin scheduler нужен для tick-timed gameplay callbacks, а не для неконтролируемого background IO.

Core task runner

core/task.Runner - низкоуровневый bounded worker pool. Submit(ctx, name, fn) принимает named task или отклоняет ее, если queue full/closed.

  • Task handle дает ID, Name, Done, Cancel и Await.
  • Result хранит value, error, start time и finish time.
  • Runner.Stats() показывает queue depth/capacity, running tasks, completions, failures, cancellations и rejects.
  • plugin.ManagerConfig дает host-level настройки TaskWorkers и TaskQueue; host может читать plugin.Manager.TaskStats().

Managed timers

core/task.TimerScheduler используется plugin scheduler и заменяет one-goroutine-per-delay scheduling на один owner-managed loop.

  • Later(ctx, delay, fn) выполняется один раз, если не отменен.
  • Repeat(ctx, period, fn) выполняется до отмены.
  • Close() отменяет pending timers и останавливает loop.
  • ctx.Scheduler().Later/Repeat по-прежнему отменяется при plugin unload.

Переведенные services

ctx.WorldTemplates().Copy(...) теперь идет через core task runner. Completion callback возвращается через plugin scheduler.

world-template-task.go
taskHandle, err := ctx.WorldTemplates().CopyTask(ctx, plugin.WorldTemplateCopyRequest{	TemplatePath: "worlds/templates/duel",	TargetPath:   "worlds/arenas/duel-1",	WorldID:      "duel-1",})if err != nil {	return err}result := taskHandle.Await(ctx)if result.Err != nil {	return result.Err}
world-lifecycle-task.go
preload, err := worldHandle.PreloadChunksTask(ctx, world.Pos{X: 0, Y: 64, Z: 0}, 8)if err != nil {	return err}if result := preload.Await(ctx); result.Err != nil {	return result.Err}save, err := worldHandle.SaveTask(ctx)if err != nil {	return err}_ = save
  • WorldHandle дает PreloadChunksTask и SaveTask для первых world lifecycle операций.
  • PlayerDataProvider дает OfflineTask, ByNameTask, ByXUIDTask, ByUUIDTask для async persisted-data reads.
  • ctx.ResourcePacks() дает RegisterFileTask, ChunkTask и Packs() для task-backed file IO.
  • World generation queue saturation возвращает world.ErrGenerationQueueFull и увеличивает saturation stats, а не создает ожидающую goroutine.
resource-pack-task.go
packTask, err := ctx.ResourcePacks().RegisterFileTask(ctx, "packs/hub.mcpack", plugin.ResourcePackOptions{})if err != nil {	return err}packResult := packTask.Await(ctx)if packResult.Err != nil {	return packResult.Err}