diff options
| author | Gene Pasquet <dev@etenil.net> | 2026-04-18 02:47:10 +0100 |
|---|---|---|
| committer | Gene Pasquet <dev@etenil.net> | 2026-04-18 02:47:10 +0100 |
| commit | 38eee24832fe6da4f135cae455881ab97953b23a (patch) | |
| tree | cffc2bb3b45ac11d90f4a2de3e207f65862fb6fd /docs/guide.org | |
| parent | a02b892e2ad1e1605ff942c63afdd618daa48be4 (diff) | |
Refresh docs and re-indent
Diffstat (limited to 'docs/guide.org')
| -rw-r--r-- | docs/guide.org | 439 |
1 files changed, 209 insertions, 230 deletions
diff --git a/docs/guide.org b/docs/guide.org index ede9180..1b16d92 100644 --- a/docs/guide.org +++ b/docs/guide.org @@ -1,302 +1,281 @@ -#+TITLE: Downstroke — Getting Started +#+TITLE: Getting Started with Downstroke #+AUTHOR: Downstroke Project -* Introduction +* Welcome -Downstroke is a 2D tile-driven game engine for Chicken Scheme, built on SDL2. It is inspired by Phaser 2: a minimal game is about 20 lines of Scheme. +Downstroke is a 2D tile-driven game engine for CHICKEN Scheme, built on SDL2. Its API is inspired by Phaser 2: a minimal game is about twenty lines of Scheme, with the engine taking care of window creation, the main loop, input, and rendering. This guide walks you through building your very first Downstroke game — a blue square you can push around the screen with the arrow keys. By the end you will have the complete source of the =getting-started= demo and a clear map of where to go next. -The engine handles SDL2 initialization, the event loop, input, rendering, and — by default — a full physics pipeline each frame (~default-engine-update~). You provide lifecycle hooks to customize behavior; ~update:~ is where you usually set movement *intent* (~#:vx~, ~#:vy~, ~#:ay~) and game rules. This guide walks you through building your first game with Downstroke. +You write a Downstroke game by calling ~make-game~ with a few keyword arguments (a title, a size, and one or two lifecycle hooks), and then handing the result to ~game-run!~. Everything else — opening the window, polling input, clearing the framebuffer, presenting the frame — is the engine's job. You only describe /what/ the game is: what the scene looks like, what the entities are, and how they change. -* Installation +* The minimum you need -** System Dependencies +The smallest Downstroke program you can write opens a window, runs the main loop, and quits when you press =Escape=. No scene, no entities, no update logic — just the lifecycle shell: -Downstroke requires the following system libraries: - -- =SDL2= -- =SDL2_mixer= -- =SDL2_ttf= -- =SDL2_image= -- =expat= +#+begin_src scheme +(import scheme + (chicken base) + downstroke-engine) -On Debian/Ubuntu: -#+begin_src bash -sudo apt-get install libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev libsdl2-image-dev libexpat1-dev +(game-run! + (make-game + title: "Hello Downstroke" + width: 320 + height: 240)) #+end_src -On macOS with Homebrew: -#+begin_src bash -brew install sdl2 sdl2_mixer sdl2_ttf sdl2_image expat -#+end_src +Save that as =hello.scm=, compile, and run. You should get a 320×240 window with a black background. Press =Escape= to quit. -** Chicken Eggs +Two things are worth noticing: -Install the Downstroke egg along with its dependencies: +- ~make-game~ takes /keyword arguments/ (note the trailing colon: ~title:~, ~width:~, ~height:~). They all have defaults, so you can leave any of them off. The window defaults to 640×480 and titled "Downstroke Game". +- ~game-run!~ is what actually starts SDL2 and enters the main loop. It blocks until the player quits. -#+begin_src bash -chicken-install downstroke -#+end_src +There are four lifecycle hooks you can attach to ~make-game~: -This pulls in the eggs declared by the Downstroke package. If your game uses the =states= egg for AI or other state machines, install it separately (=chicken-install states=). +| Keyword | When it runs | Signature | +|------------+-------------------------------+--------------------------| +| ~preload:~ | once, before ~create:~ | ~(lambda (game) ...)~ | +| ~create:~ | once, after ~preload:~ | ~(lambda (game) ...)~ | +| ~update:~ | every frame | ~(lambda (game dt) ...)~ | +| ~render:~ | every frame, after scene draw | ~(lambda (game) ...)~ | -* Hello World — Your First Game -Create a file called =mygame.scm=: - -#+begin_src scheme -(import downstroke-engine) - -(define *game* - (make-game title: "Hello World" width: 640 height: 480)) - -(game-run! *game*) -#+end_src +In the rest of this guide you will fill in ~create:~ (to build the scene) and ~update:~ (to move the player). -Build and run: -#+begin_src bash -csc mygame.scm -o mygame -./mygame -#+end_src +* Core concepts -You should see a black window titled "Hello World". Press =Escape= or close the window to quit. The =game-run!= function handles SDL2 initialization, window creation, the event loop, and cleanup automatically. The engine also provides a default =quit= action (Escape key and window close button). +** Creating a scene -* Moving Square — First Entity +A /scene/ is the container for everything the engine draws and simulates: a list of entities, an optional tilemap, a camera, and a background color. You build one with ~make-scene~ or — for sprite-only games like ours — with the simpler ~make-sprite-scene~, which skips the tilemap fields. -Now let's add an entity you can move with the keyboard. Create =square.scm=: +In the ~create:~ hook you typically build a scene and hand it to the game with ~game-scene-set!~: #+begin_src scheme (import scheme (chicken base) - (prefix sdl2 "sdl2:") downstroke-engine downstroke-world - downstroke-entity - downstroke-input) - -(define *game* - (make-game - title: "Moving Square" width: 640 height: 480 - - create: (lambda (game) - (game-scene-set! game - (make-scene - entities: (list (list #:type 'box #:x 300 #:y 200 - #:width 32 #:height 32 - #:vx 0 #:vy 0)) - tilemap: #f - camera: (make-camera x: 0 y: 0) - tileset-texture: #f))) - - update: (lambda (game dt) - (let* ((input (game-input game)) - (scene (game-scene game)) - (box (car (scene-entities scene))) - ;; Intent only: default engine-update integrates #:vx / #:vy each frame - (box (entity-set box #:vx (cond ((input-held? input 'left) -3) - ((input-held? input 'right) 3) - (else 0)))) - (box (entity-set box #:vy (cond ((input-held? input 'up) -3) - ((input-held? input 'down) 3) - (else 0))))) - (game-scene-set! game - (update-scene scene entities: (list box))))) - - render: (lambda (game) - (let* ((scene (game-scene game)) - (box (car (scene-entities scene))) - (x (inexact->exact (floor (entity-ref box #:x 0)))) - (y (inexact->exact (floor (entity-ref box #:y 0)))) - (w (entity-ref box #:width 32)) - (h (entity-ref box #:height 32))) - (sdl2:set-render-draw-color! (game-renderer game) 255 200 0 255) - (sdl2:render-fill-rect! (game-renderer game) - (sdl2:make-rect x y w h)))))) - -(game-run! *game*) + downstroke-scene-loader) + +(game-run! + (make-game + title: "Blue Window" + width: 320 height: 240 + create: (lambda (game) + (game-scene-set! game + (make-sprite-scene + entities: '() + background: '(20 22 30)))))) #+end_src -Run it: -#+begin_src bash -csc square.scm -o square -./square -#+end_src +The ~background:~ argument is a list of three or four integers ~(r g b)~ or ~(r g b a)~ in the 0–255 range. The engine clears the framebuffer with this color at the top of every frame. If you omit it, the background is plain black. -Press arrow keys to move the yellow square around. Here are the key ideas: +~make-sprite-scene~ also accepts ~camera:~, ~camera-target:~, ~tileset:~, ~tileset-texture:~, and ~engine-update:~, but you do not need any of them for the guide. -- **Scenes**: =make-scene= creates a container for entities, tilemaps, and the camera. It holds the game state each frame. Optional =background:= ~(r g b)~ or ~(r g b a)~ sets the color used to clear the window each frame (default is black). -- **Entities**: Entities are plists (property lists). They have no class; they're pure data. Access properties with =entity-ref=, and update with =entity-set= (which returns a *new* plist — always bind the result). -- **Input**: =input-held?= returns =#t= if an action is currently pressed. Actions are symbols like ='left=, ='right=, ='up=, ='down= (from the default input config). -- **Update & Render**: Each frame, after input, the built-in =engine-update= integrates =#:vx= / =#:vy= into position (gravity and tile steps no-op without =#:gravity?= or a tilemap). Your =update:= hook runs *after* that and sets velocities for the following frame. The =render:= hook runs after the default scene render and is used for custom drawing like this colored rectangle. -- **Rendering**: Since our tilemap is =#f=, the default renderer draws nothing; all drawing happens in the =render:= hook using SDL2 functions. +** Adding an entity -* Adding a Tilemap and Physics +In Downstroke an /entity/ is just an alist — a list of ~(key . value)~ pairs — with a small set of conventional keyword keys. There are no classes and no inheritance; an entity is data you can read with ~entity-ref~ and transform with ~entity-set~. -For a real game, you probably want tilemaps, gravity, and collision detection. Downstroke includes a full physics pipeline. Here's the pattern: +It is common to write entities in plist form (alternating keys and values) for readability and then convert them with ~plist->alist~ from the =list-utils= egg: #+begin_src scheme -(import scheme - (chicken base) - downstroke-engine - downstroke-world - downstroke-entity - downstroke-input - downstroke-physics - downstroke-scene-loader - downstroke-sound) - -(define *game* - (make-game - title: "Platformer Demo" width: 600 height: 400 - - preload: (lambda (game) - ;; Initialize audio and load sounds (optional) - (init-audio!) - (load-sounds! '((jump . "assets/jump.wav")))) - - create: (lambda (game) - ;; Load the tilemap from a TMX file (made with Tiled editor) - (let* ((scene (game-load-scene! game "assets/level.tmx")) - ;; Create a player entity - (player (list #:type 'player - #:x 100 #:y 50 - #:width 16 #:height 16 - #:vx 0 #:vy 0 - #:gravity? #t - #:on-ground? #f - #:tile-id 1))) - ;; Add player to the scene - (scene-add-entity scene player))) - - update: (lambda (game dt) - ;; Game logic only — gravity, collisions, and #:on-ground? run in default-engine-update - (let* ((input (game-input game)) - (scene (game-scene game)) - (player (car (scene-entities scene))) - (jump? (and (input-pressed? input 'a) - (entity-ref player #:on-ground? #f))) - (player (entity-set player #:vx - (cond - ((input-held? input 'left) -3) - ((input-held? input 'right) 3) - (else 0))))) - (when jump? (play-sound 'jump)) - (let ((player (if jump? - (entity-set player #:ay (- *jump-force*)) - player))) - (game-scene-set! game - (update-scene scene entities: (list player))))))) - -(game-run! *game*) +(import (only (list-utils alist) plist->alist)) + +(define (make-player) + (plist->alist + (list #:type 'player + #:x 150 #:y 100 + #:width 32 #:height 32 + #:color '(100 160 255)))) +#+end_src + +The keys in use here are the conventional ones the engine understands: + +- ~#:type~ — a symbol you use to distinguish entities; purely for your own bookkeeping. +- ~#:x~, ~#:y~ — pixel position of the entity's top-left corner. +- ~#:width~, ~#:height~ — pixel size of the entity's bounding box. +- ~#:color~ — an ~(r g b)~ or ~(r g b a)~ list. When an entity has no ~#:tile-id~ (or the scene has no tileset texture), the renderer fills the entity's rectangle with this color. That is exactly what we want for our guide: no sprite sheet, just a colored box. + +Once you have a player factory, drop one into the scene's ~entities:~ list: + +#+begin_src scheme +(make-sprite-scene + entities: (list (make-player)) + background: '(20 22 30)) #+end_src -Key points: +Compile and run, and you should see a light-blue 32×32 square on a dark background. It does not move yet — that is the next step. + +** Reading input + +Input in Downstroke is organised around /actions/ rather than raw keys. The engine ships with a default input config (~*default-input-config*~ in ~downstroke-input~) that maps the arrow keys, WASD, a couple of action buttons, and game-controller buttons to a small set of action symbols: -- **=game-load-scene!=** loads a TMX tilemap file (created with the Tiled editor), creates the tileset texture, and builds the scene. It returns the scene so you can add more entities. -- **=init-audio!=** and **=load-sounds!=** initialize the audio subsystem and load sound files. Call these in the =preload:= hook. -- **=play-sound=** plays a loaded sound effect. -- **Automatic physics**: By default, =make-game= uses =default-engine-update=, which runs the full pipeline (tweens, acceleration, gravity, velocity, tile and entity collisions, ground detection, group sync) *before* your =update:= hook. Your =update:= sets =#:vx= and a one-frame =#:ay= for jumps; =*jump-force*= (from =downstroke-physics=, default 15) is a conventional jump impulse. -- **=engine-update: #f=** disables that pipeline — use this for games that move entities entirely in =update:= (see =demo/shmup.scm=, =demo/scaling.scm=) or supply your own =engine-update:= procedure to replace =default-engine-update=. -- **=detect-on-solid=** (inside the default pipeline) sets =#:on-ground?= from tiles below the feet and from other solids in the scene entity list. When your =update:= runs, that flag already reflects the current frame. +| Action | Default keys | +|----------+-------------------| +| ~up~ | =Up=, =W= | +| ~down~ | =Down=, =S= | +| ~left~ | =Left=, =A= | +| ~right~ | =Right=, =D= | +| ~a~ | =J=, =Z= | +| ~b~ | =K=, =X= | +| ~start~ | =Return= | +| ~select~ | (controller only) | +| ~quit~ | =Escape= | -See =demo/platformer.scm= in the engine source for a complete working example. -* Development Tips +Inside ~update:~ you reach the input state with ~(game-input game)~, and then query it with three predicates from ~downstroke-input~: -** Pixel Scaling +- ~(input-held? input 'left)~ — ~#t~ while the player holds the action down. +- ~(input-pressed? input 'a)~ — ~#t~ only on the first frame the action goes down (edge). +- ~(input-released? input 'a)~ — ~#t~ only on the frame the action goes up. -Retro-style games often use a small logical resolution (e.g. 320×240) but need a larger window so players can actually see things. Use ~scale:~ to scale everything uniformly: +~input-held?~ is what we want for continuous movement: #+begin_src scheme -(make-game title: "Pixel Art" width: 320 height: 240 scale: 2) -;; Creates a 640×480 window; all coordinates remain in 320×240 space +(define +speed+ 2) + +(define (input-dx input) + (cond ((input-held? input 'left) (- +speed+)) + ((input-held? input 'right) +speed+) + (else 0))) + +(define (input-dy input) + (cond ((input-held? input 'up) (- +speed+)) + ((input-held? input 'down) +speed+) + (else 0))) #+end_src -This is integer-only scaling. All rendering — tiles, sprites, text, debug overlays — is scaled automatically by SDL2. Game logic and coordinates are unaffected. +~+speed+~ is pixels per frame. At the engine's default 16 ms frame delay that works out to roughly 120 pixels per second; feel free to adjust. -See =demo/scaling.scm= for a complete example. +The =quit= action is already wired into the main loop: pressing =Escape= ends the game, so you do not need to handle it yourself. -** Fullscreen +** Updating entity state -Use SDL2 window flags in the ~preload:~ hook to go fullscreen: +Entities are immutable. ~entity-set~ does not mutate; it returns a /new/ alist with the key updated. To move the player, read its current position, compute the new position, and produce a new entity: #+begin_src scheme -(make-game - title: "Fullscreen Game" width: 320 height: 240 scale: 2 - preload: (lambda (game) - (sdl2:window-fullscreen-set! (game-window game) 'fullscreen-desktop))) +(define (move-player player input) + (let* ((x (entity-ref player #:x 0)) + (y (entity-ref player #:y 0)) + (dx (input-dx input)) + (dy (input-dy input))) + (entity-set (entity-set player #:x (+ x dx)) #:y (+ y dy)))) #+end_src -~'fullscreen-desktop~ fills the screen without changing display resolution; SDL2 handles letterboxing and scaling. ~'fullscreen~ uses exclusive fullscreen at the window size. - -** Debug Mode +~entity-ref~ takes an optional default (~0~ above), returned if the key is absent. -During development, enable ~debug?: #t~ on ~make-game~ to visualize collision boxes and the tile grid. This helps you understand what the physics engine "sees" and debug collision problems: +Once you have a new entity, you also need a new scene that contains it, because scenes are immutable too. ~update-scene~ is the copier: pass it an existing scene and any fields you want to change. #+begin_src scheme -(define my-game - (make-game - title: "My Game" width: 600 height: 400 - debug?: #t)) ;; Enable collision visualization - -(game-run! my-game) +(update-scene scene + entities: (list (move-player player input))) #+end_src -With debug mode enabled, you'll see: +Finally, install the new scene on the game with ~game-scene-set!~. Putting it all together in the ~update:~ hook: + +#+begin_src scheme +update: (lambda (game dt) + (let* ((scene (game-scene game)) + (input (game-input game)) + (player (car (scene-entities scene)))) + (game-scene-set! game + (update-scene scene + entities: (list (move-player player input)))))) +#+end_src -- **Purple outlines** around all non-zero tiles -- **Blue boxes** around player entities -- **Red boxes** around enemy entities -- **Green boxes** around active attack hitboxes +A note about ~dt~: the update hook is called with the number of milliseconds elapsed since the previous frame. We ignore it here because we move by a fixed number of pixels per frame, but most real games use ~dt~ to make motion frame-rate independent — see [[file:physics.org][physics.org]] for the engine's built-in pipeline which does this for you. -This is invaluable for tuning collision geometry and understanding why entities are clipping or not colliding as expected. +* Putting it together -* Demo Overview +Here is the full =demo/getting-started.scm= source. Read it top to bottom — each piece should now look familiar. -Downstroke includes several demo games that showcase different features: +#+begin_src scheme +(import scheme + (chicken base) + (only (list-utils alist) plist->alist) + downstroke-engine + downstroke-world + downstroke-entity + downstroke-input + downstroke-scene-loader) + +(define +speed+ 2) + +(define (make-player) + (plist->alist + (list #:type 'player + #:x 150 #:y 100 + #:width 32 #:height 32 + #:color '(100 160 255)))) + +(define (move-player player input) + (let* ((x (entity-ref player #:x 0)) + (y (entity-ref player #:y 0)) + (dx (cond ((input-held? input 'left) (- +speed+)) + ((input-held? input 'right) +speed+) + (else 0))) + (dy (cond ((input-held? input 'up) (- +speed+)) + ((input-held? input 'down) +speed+) + (else 0)))) + (entity-set (entity-set player #:x (+ x dx)) #:y (+ y dy)))) + +(game-run! + (make-game + title: "Getting Started" + width: 320 height: 240 + + create: (lambda (game) + (game-scene-set! game + (make-sprite-scene + entities: (list (make-player)) + background: '(20 22 30)))) + + update: (lambda (game dt) + (let* ((scene (game-scene game)) + (input (game-input game)) + (player (car (scene-entities scene)))) + (game-scene-set! game + (update-scene scene + entities: (list (move-player player input)))))))) +#+end_src -| Demo | File | What it shows | -|------|------|--------------| -| Platformer | =demo/platformer.scm= | Gravity, jump, tile collision, camera follow, sound effects | -| Top-down | =demo/topdown.scm= | 8-directional movement, no gravity, tilemap, camera follow | -| Physics Sandbox | =demo/sandbox.scm= | Entity-entity collision, multi-entity physics, auto-respawn | -| Shoot-em-up | =demo/shmup.scm= | No tilemap, entity spawning/removal, manual AABB collision | -| Audio | =demo/audio.scm= | Sound effects, music toggle, text rendering | -| Sprite Font | =demo/spritefont.scm= | Bitmap text rendering using non-contiguous tileset ranges | -| Menu | =demo/menu.scm= | State machine menus, keyboard navigation, TTF text rendering | -| Tweens | =demo/tweens.scm= | Easing curves, =tween-step=, =#:skip-pipelines= with tile collision | -| Scaling | =demo/scaling.scm= | Integer pixel scaling with =scale: 2=, 2× upscaled rendering | +This file ships with the engine. To run it, build the project and launch the demo binary: -Each demo is self-contained and serves as a working reference for a particular game mechanic. +*Getting started demo* — run with =bin/demo-getting-started=, source in =demo/getting-started.scm=. -* Build & Run +From the Downstroke source tree, ~make~ will compile the engine and ~make demos~ will build every demo executable under =bin/=. When the binary starts you should see a 320×240 window with a blue square you can push around with the arrow keys (or =WASD=). =Escape= quits. -If you cloned the downstroke source, you can build everything: +A few things to notice in the final code: -#+begin_src bash -cd /path/to/downstroke -make # Compile all engine modules -make demos # Build all demo executables -./bin/demo-platformer -./bin/demo-topdown -# etc. -#+end_src +- We never mutate anything. ~move-player~ returns a new entity; ~update-scene~ returns a new scene; ~game-scene-set!~ swaps the scene on the ~game~ struct for the next frame. +- Every frame, ~update:~ rebuilds the entire entities list from scratch. That is fine for a one-entity demo and scales happily into the dozens; for larger games the engine's built-in physics pipeline (see [[file:physics.org][physics.org]]) does the same work for you using keyword conventions like ~#:vx~ and ~#:vy~. +- The engine's /default engine update/ — the physics pipeline — still runs before your ~update:~ hook. It is a no-op for our player because we never set velocity keys like ~#:vx~ or ~#:vy~, so the only thing moving the square is your own ~move-player~. The moment you set ~#:vx~ to a non-zero number, the engine will start applying it for you. -* Next Steps +* Where to next -You now know how to: +Once =getting-started= runs, the rest of Downstroke is a menu of subsystems you can opt into piece by piece. Every topic below has its own doc file and at least one matching demo: -- Create a game with =make-game= and =game-run!= -- Add entities to scenes -- Read input with =input-held?= and =input-pressed?= -- Rely on the default physics pipeline (or turn it off with =engine-update: #f=) -- Load tilemaps with =game-load-scene!= -- Play sounds with =load-sounds!= and =play-sound= +- [[file:entities.org][entities.org]] — the entity model, the list of conventional keys, and how to build reusable prefabs. +- [[file:physics.org][physics.org]] — the built-in frame pipeline: gravity, velocity, tile collisions, ground detection, and entity-vs-entity collisions. +- [[file:scenes.org][scenes.org]] — scenes, cameras, camera follow, and switching between named game states. +- [[file:rendering.org][rendering.org]] — how the renderer draws sprites, solid-color rects, and bitmap sprite fonts. +- [[file:animation.org][animation.org]] — frame-based sprite animation driven by ~#:animations~ and ~#:anim-name~. +- [[file:input.org][input.org]] — customising the action list, keyboard map, and game-controller bindings. +- [[file:tweens.org][tweens.org]] — declarative easing for position, scale, color, and other numeric keys. +- [[file:audio.org][audio.org]] — loading and playing sound effects and music. -For more details: +And the full set of demos that ship with the repository, each built by ~make demos~: -- **Full API reference**: See =docs/api.org= for all functions and keyword arguments. -- **Entity model**: See =docs/entities.org= to learn about plist keys, tags, prefabs, and mixins. -- **Physics pipeline**: See =docs/physics.org= for the full physics specification and collision model. -- **Tweens**: See =docs/tweens.org= for time-based property interpolation and combining tweens with physics. +- [[file:../demo/getting-started.scm][Getting started]] (=bin/demo-getting-started=) — arrow keys move a blue square; the starting point for this guide. +- [[file:../demo/platformer.scm][Platformer]] (=bin/demo-platformer=) — gravity, jumping, tile collisions, frame animation. +- [[file:../demo/shmup.scm][Shmup]] (=bin/demo-shmup=) — top-down shooter-style movement and firing. +- [[file:../demo/topdown.scm][Topdown]] (=bin/demo-topdown=) — four-direction movement without gravity. +- [[file:../demo/audio.scm][Audio]] (=bin/demo-audio=) — background music and sound effects. +- [[file:../demo/sandbox.scm][Sandbox]] (=bin/demo-sandbox=) — group prefabs composed from mixins. +- [[file:../demo/spritefont.scm][Sprite font]] (=bin/demo-spritefont=) — bitmap font rendering. +- [[file:../demo/menu.scm][Menu]] (=bin/demo-menu=) — a simple UI menu. +- [[file:../demo/tweens.scm][Tweens]] (=bin/demo-tweens=) — side-by-side comparison of easing functions. +- [[file:../demo/scaling.scm][Scaling]] (=bin/demo-scaling=) — logical-resolution scaling via ~scale:~. +- [[file:../demo/animation.scm][Animation]] (=bin/demo-animation=) — frame-based sprite animation. -Happy coding! +Pick the demo closest to the game you want to build, open its source next to the matching topic doc, and you will have a concrete example of every API call in context. |
