or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation.mdapplication-framework.mdasset-loading.mdaudio.mdindex.mdinput.mdmathematics.mdscene-graph.mdtask-event.mduser-interface.md

task-event.mddocs/

0

# Task and Event Management

1

2

Panda3D provides a comprehensive system for frame-based task scheduling and event-driven programming, essential for game logic, animations, and inter-object communication.

3

4

## Capabilities

5

6

### TaskManager - Frame-Based Task Scheduling

7

8

The TaskManager handles frame-based tasks that run continuously or periodically, forming the backbone of game logic execution.

9

10

```python { .api }

11

class TaskManager:

12

def add(self,

13

task: callable,

14

name: str,

15

priority: int = 0,

16

uponDeath: callable = None,

17

appendTask: bool = False,

18

owner: object = None) -> Task:

19

"""

20

Add a task to run every frame.

21

22

Args:

23

task: Function to call each frame, must return task.cont or task.done

24

name: Unique name for the task

25

priority: Task priority (lower numbers run first)

26

uponDeath: Function to call when task completes

27

appendTask: Whether to append Task object as last argument

28

owner: Object that owns this task

29

30

Returns:

31

Task object for management

32

"""

33

34

def remove(self, taskOrName: str | Task) -> bool:

35

"""Remove a task by name or Task object."""

36

37

def removeTasksMatching(self, pattern: str) -> int:

38

"""Remove all tasks with names matching glob pattern."""

39

40

def step(self) -> None:

41

"""Execute one frame of all tasks."""

42

43

def run(self) -> None:

44

"""Run task manager main loop (blocking)."""

45

46

def stop(self) -> None:

47

"""Stop the task manager main loop."""

48

49

def hasTaskNamed(self, name: str) -> bool:

50

"""Check if task with given name exists."""

51

52

def getTasksNamed(self, name: str) -> List[Task]:

53

"""Get all tasks with given name."""

54

55

def getAllTasks(self) -> List[Task]:

56

"""Get list of all active tasks."""

57

58

def getTasks(self) -> List[Task]:

59

"""Get list of all tasks."""

60

61

def getNumTasks(self) -> int:

62

"""Get total number of active tasks."""

63

64

def doMethodLater(self,

65

delayTime: float,

66

task: callable,

67

name: str,

68

extraArgs: List = [],

69

priority: int = 0,

70

uponDeath: callable = None,

71

owner: object = None) -> Task:

72

"""Schedule a task to run after a delay."""

73

74

def doMethodLaterPriority(self,

75

delayTime: float,

76

task: callable,

77

name: str,

78

priority: int,

79

extraArgs: List = []) -> Task:

80

"""Schedule a delayed task with specific priority."""

81

```

82

83

### Task Objects and Control

84

85

Individual task objects provide fine-grained control over task execution and state.

86

87

```python { .api }

88

class Task:

89

# Task return constants

90

cont: int # Continue running task next frame

91

done: int # Task completed, remove from manager

92

again: int # Restart task from beginning

93

94

def __init__(self, function: callable = None, name: str = None) -> None:

95

"""Create new task object."""

96

97

def setFunction(self, function: callable) -> None:

98

"""Set task function."""

99

100

def getFunction(self) -> callable:

101

"""Get task function."""

102

103

def setName(self, name: str) -> None:

104

"""Set task name."""

105

106

def getName(self) -> str:

107

"""Get task name."""

108

109

def setPriority(self, priority: int) -> None:

110

"""Set task priority."""

111

112

def getPriority(self) -> int:

113

"""Get task priority."""

114

115

def setDelay(self, delay: float) -> None:

116

"""Set delay before task starts."""

117

118

def getDelay(self) -> float:

119

"""Get task delay."""

120

121

def setUponDeath(self, function: callable) -> None:

122

"""Set function to call when task completes."""

123

124

def getUponDeath(self) -> callable:

125

"""Get upon-death function."""

126

127

def remove(self) -> None:

128

"""Remove this task from manager."""

129

130

def pause(self) -> None:

131

"""Pause task execution."""

132

133

def resume(self) -> None:

134

"""Resume paused task."""

135

136

def isPaused(self) -> bool:

137

"""Check if task is paused."""

138

139

def getElapsedTime(self) -> float:

140

"""Get time elapsed since task started."""

141

142

def getElapsedFrames(self) -> int:

143

"""Get frames elapsed since task started."""

144

145

def getWakeTime(self) -> float:

146

"""Get time when delayed task will wake up."""

147

148

# Task state properties

149

time: float # Current task time

150

frame: int # Current frame count

151

dt: float # Delta time since last frame

152

id: int # Unique task ID

153

```

154

155

### Messenger - Event System

156

157

The Messenger provides a global publish-subscribe event system for inter-object communication.

158

159

```python { .api }

160

class Messenger:

161

def accept(self,

162

event: str,

163

method: callable,

164

extraArgs: List = [],

165

persistent: int = 1) -> None:

166

"""

167

Accept an event and bind it to a method.

168

169

Args:

170

event: Event name to listen for

171

method: Function to call when event occurs

172

extraArgs: Additional arguments to pass to method

173

persistent: How many times to accept event (0 = forever)

174

"""

175

176

def acceptOnce(self,

177

event: str,

178

method: callable,

179

extraArgs: List = []) -> None:

180

"""Accept an event only once."""

181

182

def ignore(self, event: str) -> None:

183

"""Stop accepting a specific event."""

184

185

def ignoreAll(self) -> None:

186

"""Stop accepting all events."""

187

188

def isAccepting(self, event: str) -> bool:

189

"""Check if currently accepting an event."""

190

191

def getAllAccepting(self) -> List[str]:

192

"""Get list of all accepted events."""

193

194

def send(self, event: str, sentArgs: List = []) -> None:

195

"""Send an event to all listeners."""

196

197

def clear(self) -> None:

198

"""Clear all event handlers."""

199

200

def isEmpty(self) -> bool:

201

"""Check if messenger has no handlers."""

202

203

def replaceMethod(self,

204

oldMethod: callable,

205

newMethod: callable) -> int:

206

"""Replace method in all event handlers."""

207

208

def toggleVerbose(self) -> None:

209

"""Toggle verbose event logging."""

210

211

def setVerbose(self, verbose: bool) -> None:

212

"""Enable or disable verbose event logging."""

213

```

214

215

### DirectObject - Event-Aware Base Class

216

217

DirectObject provides a convenient base class that integrates with the event system.

218

219

```python { .api }

220

class DirectObject:

221

def __init__(self) -> None:

222

"""Initialize DirectObject with event handling."""

223

224

def accept(self,

225

event: str,

226

method: callable,

227

extraArgs: List = []) -> None:

228

"""Accept event (automatically cleaned up on destruction)."""

229

230

def acceptOnce(self,

231

event: str,

232

method: callable,

233

extraArgs: List = []) -> None:

234

"""Accept event once."""

235

236

def ignore(self, event: str) -> None:

237

"""Ignore specific event."""

238

239

def ignoreAll(self) -> None:

240

"""Ignore all events for this object."""

241

242

def isAccepting(self, event: str) -> bool:

243

"""Check if accepting event."""

244

245

def getAllAccepting(self) -> List[str]:

246

"""Get all accepted events."""

247

248

def destroy(self) -> None:

249

"""Clean up object and remove all event handlers."""

250

251

def addTask(self,

252

task: callable,

253

name: str,

254

priority: int = 0,

255

uponDeath: callable = None,

256

appendTask: bool = False) -> Task:

257

"""Add task (automatically cleaned up on destruction)."""

258

259

def doMethodLater(self,

260

delayTime: float,

261

task: callable,

262

name: str,

263

extraArgs: List = []) -> Task:

264

"""Schedule delayed task."""

265

266

def removeTask(self, taskOrName: str | Task) -> None:

267

"""Remove specific task."""

268

269

def removeAllTasks(self) -> None:

270

"""Remove all tasks for this object."""

271

```

272

273

### Common Event Types

274

275

Standard events generated by Panda3D systems.

276

277

```python { .api }

278

# Window events

279

"window-event" # Window state changes

280

"window-close-request" # User requests window close

281

282

# Input events

283

"escape" # Escape key pressed

284

"space" # Space key pressed

285

"enter" # Enter key pressed

286

"mouse1" # Left mouse button pressed

287

"mouse1-up" # Left mouse button released

288

"mouse2" # Middle mouse button pressed

289

"mouse2-up" # Middle mouse button released

290

"mouse3" # Right mouse button pressed

291

"mouse3-up" # Right mouse button released

292

"wheel_up" # Mouse wheel up

293

"wheel_down" # Mouse wheel down

294

295

# Key events (format: key name, key-up)

296

"a", "a-up" # A key pressed/released

297

"shift", "shift-up" # Shift key pressed/released

298

"control", "control-up" # Control key pressed/released

299

"alt", "alt-up" # Alt key pressed/released

300

301

# Arrow keys

302

"arrow_up", "arrow_up-up"

303

"arrow_down", "arrow_down-up"

304

"arrow_left", "arrow_left-up"

305

"arrow_right", "arrow_right-up"

306

307

# Function keys

308

"f1", "f1-up" # F1 through F12

309

# ... f2 through f12

310

311

# Task events

312

"task-added" # Task added to manager

313

"task-removed" # Task removed from manager

314

```

315

316

### Clock and Timing

317

318

Access global timing information for frame-based calculations.

319

320

```python { .api }

321

class ClockObject:

322

def getDt(self) -> float:

323

"""Get delta time since last frame."""

324

325

def getFrameTime(self) -> float:

326

"""Get time of current frame."""

327

328

def getRealTime(self) -> float:

329

"""Get real elapsed time since start."""

330

331

def getFrameCount(self) -> int:

332

"""Get total frame count."""

333

334

def getAverageFrameRate(self) -> float:

335

"""Get average frame rate."""

336

337

def getMaxDt(self) -> float:

338

"""Get maximum allowed delta time."""

339

340

def setMaxDt(self, maxDt: float) -> None:

341

"""Set maximum delta time limit."""

342

343

def tick(self) -> None:

344

"""Advance clock by one frame."""

345

346

# Global clock instance

347

globalClock: ClockObject

348

```

349

350

## Usage Examples

351

352

### Basic Task Management

353

354

```python

355

from direct.showbase.ShowBase import ShowBase

356

from direct.task import Task

357

from panda3d.core import Vec3

358

359

class TaskDemo(ShowBase):

360

def __init__(self):

361

ShowBase.__init__(self)

362

363

# Create object to animate

364

self.cube = self.loader.loadModel("models/cube")

365

self.cube.reparentTo(self.render)

366

self.cube.setPos(0, 10, 0)

367

368

# Add continuous rotation task

369

self.taskMgr.add(self.rotateCube, "rotate-cube")

370

371

# Add periodic task

372

self.taskMgr.doMethodLater(

373

2.0, self.changeColor, "change-color"

374

)

375

376

# Task with completion

377

self.taskMgr.add(self.moveAndStop, "move-cube")

378

379

self.colors = [(1,0,0,1), (0,1,0,1), (0,0,1,1), (1,1,0,1)]

380

self.colorIndex = 0

381

self.startTime = globalClock.getFrameTime()

382

383

def rotateCube(self, task):

384

"""Rotate cube continuously."""

385

dt = globalClock.getDt()

386

387

# Rotate around Y axis

388

currentH = self.cube.getH()

389

self.cube.setH(currentH + 30 * dt) # 30 degrees per second

390

391

return task.cont # Continue running

392

393

def changeColor(self, task):

394

"""Change cube color periodically."""

395

color = self.colors[self.colorIndex]

396

self.cube.setColor(*color)

397

398

self.colorIndex = (self.colorIndex + 1) % len(self.colors)

399

400

# Schedule next color change

401

task.delayTime = 2.0 # Wait 2 seconds

402

return task.again # Restart task with delay

403

404

def moveAndStop(self, task):

405

"""Move cube for 5 seconds then stop."""

406

dt = globalClock.getDt()

407

elapsed = globalClock.getFrameTime() - self.startTime

408

409

if elapsed < 5.0:

410

# Move cube

411

currentPos = self.cube.getPos()

412

self.cube.setPos(currentPos + Vec3(dt, 0, 0))

413

return task.cont

414

else:

415

print("Movement task completed!")

416

return task.done # Task finished

417

418

app = TaskDemo()

419

app.run()

420

```

421

422

### Event-Driven Programming

423

424

```python

425

from direct.showbase.ShowBase import ShowBase

426

from direct.showbase.DirectObject import DirectObject

427

from panda3d.core import Vec3

428

429

class EventDemo(ShowBase):

430

def __init__(self):

431

ShowBase.__init__(self)

432

433

# Create game objects

434

self.player = self.loader.loadModel("models/player")

435

self.player.reparentTo(self.render)

436

self.player.setPos(0, 10, 0)

437

438

# Create game controller

439

self.controller = GameController(self.player)

440

441

# Setup system event handling

442

self.accept("escape", self.exitGame)

443

self.accept("window-close-request", self.exitGame)

444

445

# Custom game events

446

self.accept("player-moved", self.onPlayerMoved)

447

self.accept("game-over", self.onGameOver)

448

449

# Send initial event

450

messenger.send("game-started")

451

452

def onPlayerMoved(self, newPos):

453

"""Handle player movement event."""

454

print(f"Player moved to: {newPos}")

455

456

# Check win condition

457

if newPos.getX() > 10:

458

messenger.send("game-over", ["win"])

459

460

def onGameOver(self, result):

461

"""Handle game over event."""

462

print(f"Game over: {result}")

463

if result == "win":

464

print("Congratulations!")

465

466

# Restart after delay

467

self.taskMgr.doMethodLater(3.0, self.restartGame, "restart")

468

469

def restartGame(self, task):

470

"""Restart the game."""

471

self.player.setPos(0, 10, 0)

472

messenger.send("game-started")

473

return task.done

474

475

def exitGame(self):

476

"""Clean exit."""

477

print("Exiting game...")

478

self.userExit()

479

480

class GameController(DirectObject):

481

def __init__(self, player):

482

DirectObject.__init__(self)

483

self.player = player

484

self.speed = 5.0

485

486

# Accept input events

487

self.accept("arrow_left", self.moveLeft)

488

self.accept("arrow_right", self.moveRight)

489

self.accept("arrow_up", self.moveUp)

490

self.accept("arrow_down", self.moveDown)

491

492

# Accept game events

493

self.accept("game-started", self.onGameStart)

494

495

# Add update task

496

self.addTask(self.update, "controller-update")

497

498

def moveLeft(self):

499

"""Move player left."""

500

pos = self.player.getPos()

501

newPos = pos + Vec3(-1, 0, 0)

502

self.player.setPos(newPos)

503

messenger.send("player-moved", [newPos])

504

505

def moveRight(self):

506

"""Move player right."""

507

pos = self.player.getPos()

508

newPos = pos + Vec3(1, 0, 0)

509

self.player.setPos(newPos)

510

messenger.send("player-moved", [newPos])

511

512

def moveUp(self):

513

"""Move player forward."""

514

pos = self.player.getPos()

515

newPos = pos + Vec3(0, 1, 0)

516

self.player.setPos(newPos)

517

messenger.send("player-moved", [newPos])

518

519

def moveDown(self):

520

"""Move player backward."""

521

pos = self.player.getPos()

522

newPos = pos + Vec3(0, -1, 0)

523

self.player.setPos(newPos)

524

messenger.send("player-moved", [newPos])

525

526

def onGameStart(self):

527

"""Handle game start event."""

528

print("Game started! Use arrow keys to move.")

529

530

def update(self, task):

531

"""Update controller logic."""

532

# Could add continuous input handling here

533

return task.cont

534

535

app = EventDemo()

536

app.run()

537

```

538

539

### Advanced Task Patterns

540

541

```python

542

from direct.showbase.ShowBase import ShowBase

543

from direct.task import Task

544

545

class AdvancedTaskDemo(ShowBase):

546

def __init__(self):

547

ShowBase.__init__(self)

548

549

# Task with state management

550

self.addTaskWithState()

551

552

# Task chains and dependencies

553

self.addTaskChain()

554

555

# Task with cleanup

556

self.addTaskWithCleanup()

557

558

def addTaskWithState(self):

559

"""Task that maintains state across frames."""

560

def countingTask(task):

561

# Initialize state on first run

562

if not hasattr(task, 'counter'):

563

task.counter = 0

564

task.startTime = globalClock.getFrameTime()

565

566

task.counter += 1

567

elapsed = globalClock.getFrameTime() - task.startTime

568

569

print(f"Frame {task.counter}, Time: {elapsed:.2f}")

570

571

# Stop after 10 seconds

572

if elapsed > 10.0:

573

print(f"Counting task completed after {task.counter} frames")

574

return task.done

575

576

return task.cont

577

578

self.taskMgr.add(countingTask, "counting-task")

579

580

def addTaskChain(self):

581

"""Chain of dependent tasks."""

582

def task1(task):

583

"""First task in chain."""

584

if not hasattr(task, 'startTime'):

585

task.startTime = globalClock.getFrameTime()

586

print("Task 1 started")

587

588

elapsed = globalClock.getFrameTime() - task.startTime

589

if elapsed > 2.0:

590

print("Task 1 completed, starting Task 2")

591

# Start next task

592

self.taskMgr.add(self.task2, "task-2")

593

return task.done

594

595

return task.cont

596

597

self.taskMgr.add(task1, "task-1")

598

599

def task2(self, task):

600

"""Second task in chain."""

601

if not hasattr(task, 'startTime'):

602

task.startTime = globalClock.getFrameTime()

603

print("Task 2 started")

604

605

elapsed = globalClock.getFrameTime() - task.startTime

606

if elapsed > 1.5:

607

print("Task 2 completed, starting Task 3")

608

# Start final task

609

self.taskMgr.doMethodLater(0.5, self.task3, "task-3")

610

return task.done

611

612

return task.cont

613

614

def task3(self, task):

615

"""Final task in chain."""

616

print("Task 3 completed - chain finished!")

617

return task.done

618

619

def addTaskWithCleanup(self):

620

"""Task with cleanup function."""

621

def workTask(task):

622

"""Main work task."""

623

if not hasattr(task, 'workDone'):

624

task.workDone = 0

625

print("Work task started")

626

627

task.workDone += 1

628

print(f"Work progress: {task.workDone}/10")

629

630

if task.workDone >= 10:

631

print("Work task completed")

632

return task.done

633

634

return task.cont

635

636

def cleanupTask(task):

637

"""Cleanup after work completes."""

638

print("Performing cleanup...")

639

return task.done

640

641

# Add task with cleanup

642

self.taskMgr.add(

643

workTask,

644

"work-task",

645

uponDeath=cleanupTask

646

)

647

648

app = AdvancedTaskDemo()

649

app.run()

650

```

651

652

## Types

653

654

```python { .api }

655

# Task constants and types

656

Task.cont: int = 0 # Continue task

657

Task.done: int = 1 # Complete task

658

Task.again: int = 2 # Restart task

659

660

# Global instances

661

taskMgr: TaskManager # Global task manager

662

messenger: Messenger # Global messenger

663

globalClock: ClockObject # Global clock

664

665

# Priority constants

666

SORT_VERY_HIGH = -1000

667

SORT_HIGH = -100

668

SORT_DEFAULT = 0

669

SORT_LOW = 100

670

SORT_VERY_LOW = 1000

671

```