summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Djup.Native/src/LibDjup.cs10
-rw-r--r--Djup.Native/src/Types.cs27
-rw-r--r--Djup/src/Djup.fsproj4
-rw-r--r--Djup/src/Program.fs258
-rw-r--r--Djup/src/packages.lock.json3
-rw-r--r--lib/physics.c38
-rw-r--r--lib/physics.h4
-rw-r--r--lib/snapshot.c69
-rw-r--r--lib/snapshot.h24
9 files changed, 260 insertions, 177 deletions
diff --git a/Djup.Native/src/LibDjup.cs b/Djup.Native/src/LibDjup.cs
index 15dcdad..4cd99f8 100644
--- a/Djup.Native/src/LibDjup.cs
+++ b/Djup.Native/src/LibDjup.cs
@@ -26,13 +26,13 @@ public static partial class LibDjup
public static partial float vec2_length(Vec2 v);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(log_set_output))]
- public static partial Vec2 log_set_output([MarshalAs(UnmanagedType.FunctionPtr)] LoggerDelegate logger);
+ public static partial void log_set_output([MarshalAs(UnmanagedType.FunctionPtr)] LoggerDelegate logger);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(log_set_level))]
- public static partial Vec2 log_set_level(LogLevel minLevel);
+ public static partial void log_set_level(LogLevel minLevel);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_create))]
- public static partial IntPtr physics_context_create(uint snapshots);
+ public static partial IntPtr physics_context_create(int snapshots, int players, int balls);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_free))]
public static partial void physics_context_free(IntPtr context);
@@ -47,8 +47,8 @@ public static partial class LibDjup
public static partial int physics_tick(IntPtr currentSnapshot, double delta, IntPtr nextSnapshot);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(snapshot_put_player))]
- public static unsafe partial int snapshot_put_player(IntPtr snapshot, uint playerId, Player *player);
+ public static unsafe partial int snapshot_put_player(IntPtr snapshot, int playerId, Player* player);
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(snapshot_set_player_input))]
- public static partial int snapshot_set_player_input(IntPtr snapshot, uint playerId, byte input);
+ public static partial int snapshot_set_player_input(IntPtr snapshot, int playerId, byte input);
}
diff --git a/Djup.Native/src/Types.cs b/Djup.Native/src/Types.cs
index 7d9c41e..3afce83 100644
--- a/Djup.Native/src/Types.cs
+++ b/Djup.Native/src/Types.cs
@@ -1,5 +1,4 @@
using System.Diagnostics;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Djup.Native;
@@ -56,24 +55,20 @@ public struct Bullet
}
[StructLayout(LayoutKind.Sequential)]
-public struct Snapshot
+public unsafe struct Snapshot
{
- [InlineArray(Constants.WorldMaxPlayers)]
- public struct PlayerArray
- {
- private Player _element0;
- }
-
- [InlineArray(Constants.WorldMaxBullets)]
- public struct BallArray
- {
- private Bullet _element0;
- }
-
private byte active;
public uint Tick { get; }
- public PlayerArray Players { get; }
- public BallArray Bullets { get; }
+ private int playersLength;
+ private Player* players;
+ private int bulletsLength;
+ private Bullet* bullets;
+
+ public ReadOnlySpan<Player> Players =>
+ new ReadOnlySpan<Player>(players, playersLength);
+
+ public ReadOnlySpan<Bullet> Bullets =>
+ new ReadOnlySpan<Bullet>(bullets, bulletsLength);
}
public enum LogLevel
diff --git a/Djup/src/Djup.fsproj b/Djup/src/Djup.fsproj
index dad45e9..607589c 100644
--- a/Djup/src/Djup.fsproj
+++ b/Djup/src/Djup.fsproj
@@ -27,6 +27,10 @@
<MonoGameContentReference Include="Content/Content.mgcb" />
</ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\Djup.Native\src\Djup.Native.csproj" />
+ </ItemGroup>
+
<Import Project="../../Djup.Native/scripts/AddRuntimeTargetsToDepsJson.targets" />
</Project>
diff --git a/Djup/src/Program.fs b/Djup/src/Program.fs
index 2427252..45dea91 100644
--- a/Djup/src/Program.fs
+++ b/Djup/src/Program.fs
@@ -1,153 +1,165 @@
module Djup.Program
-open System
+open FSharp.NativeInterop
open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input
open Mibo.Elmish
open Mibo.Elmish.Graphics2D
open Mibo.Input
-
-// ─────────────────────────────────────────────────────────────
-// Input
-// ─────────────────────────────────────────────────────────────
+open Djup.Native
type GameAction =
- | MoveLeft
- | MoveRight
- | MoveUp
- | MoveDown
+ | MoveLeft
+ | MoveRight
+ | MoveUp
+ | MoveDown
let inputMap =
- InputMap.empty
- |> InputMap.key MoveLeft Keys.Left
- |> InputMap.key MoveLeft Keys.A
- |> InputMap.key MoveRight Keys.Right
- |> InputMap.key MoveRight Keys.D
- |> InputMap.key MoveUp Keys.Up
- |> InputMap.key MoveUp Keys.W
- |> InputMap.key MoveDown Keys.Down
- |> InputMap.key MoveDown Keys.S
-
-// ─────────────────────────────────────────────────────────────
-// Model
-// ─────────────────────────────────────────────────────────────
-
-type Model = {
- Position: Vector2
- Velocity: Vector2
- Input: ActionState<GameAction>
-}
-
-// ─────────────────────────────────────────────────────────────
-// Messages
-// ─────────────────────────────────────────────────────────────
+ InputMap.empty
+ |> InputMap.key MoveLeft Keys.Left
+ |> InputMap.key MoveLeft Keys.A
+ |> InputMap.key MoveRight Keys.Right
+ |> InputMap.key MoveRight Keys.D
+ |> InputMap.key MoveUp Keys.Up
+ |> InputMap.key MoveUp Keys.W
+ |> InputMap.key MoveDown Keys.Down
+ |> InputMap.key MoveDown Keys.S
+
+type Model =
+ { PhysicsContext: nativeint
+ CurrentSnapshot: nativeint
+ PlayerId: int }
type Msg =
- | Tick of GameTime
- | InputChanged of ActionState<GameAction>
-
-// ─────────────────────────────────────────────────────────────
-// Init
-// ─────────────────────────────────────────────────────────────
-
-let init(ctx: GameContext) : struct (Model * Cmd<Msg>) =
- let model = {
- Position = Vector2(400.f, 300.f)
- Velocity = Vector2(200.f, 150.f)
- Input = ActionState.empty
- }
-
- model, Cmd.none
-
-// ─────────────────────────────────────────────────────────────
-// Update
-// ─────────────────────────────────────────────────────────────
+ | PhysicsTick of float32
+ | Tick of GameTime
+ | InputChanged of ActionState<GameAction>
-let update (msg: Msg) (model: Model) : struct (Model * Cmd<Msg>) =
- match msg with
- | InputChanged input -> { model with Input = input }, Cmd.none
-
- | Tick gt ->
- let dt = float32 gt.ElapsedGameTime.TotalSeconds
-
- // Manual movement
- let speed = 200.f
- let mutable manualVelocity = Vector2.Zero
+let init (_: GameContext) : struct (Model * Cmd<Msg>) =
+ LibDjup.log_set_output (
+ LoggerDelegate(fun level source line message ->
+ let level =
+ match level with
+ | LogLevel.Trace -> "TRACE"
+ | LogLevel.Debug -> "DEBUG"
+ | LogLevel.Info -> "INFO "
+ | LogLevel.Warn -> "WARN "
+ | LogLevel.Error -> "ERROR"
+ | LogLevel.Fatal -> "FATAL"
+ | _ -> sprintf "UNKNOWN(%d)" (int level)
- if model.Input.Held.Contains MoveLeft then
- manualVelocity.X <- manualVelocity.X - speed
+ printfn "libdjup %s %s:%d: %s" level source line message)
+ )
- if model.Input.Held.Contains MoveRight then
- manualVelocity.X <- manualVelocity.X + speed
+ let physics = LibDjup.physics_context_create (2, 1, 1000)
- if model.Input.Held.Contains MoveUp then
- manualVelocity.Y <- manualVelocity.Y - speed
+ if physics = 0 then
+ failwith "Failed to allocate physics context"
- if model.Input.Held.Contains MoveDown then
- manualVelocity.Y <- manualVelocity.Y + speed
+ let snapshot = LibDjup.physics_context_get_snapshot physics
- // Bouncing logic
- let mutable velocity = model.Velocity
+ let putPlayer playerId player =
+ use ptr = fixed &player
+ LibDjup.snapshot_put_player (snapshot, playerId, ptr)
- let mutable position =
- model.Position + (velocity * dt) + (manualVelocity * dt)
+ let playerId = 0
- if position.X < 0.f || position.X > 800.f - 32.f then
- velocity.X <- -velocity.X
+ match putPlayer playerId (Player()) with
+ | 0 -> ()
+ | errorCode -> failwithf "Failed to put player (error code %d)" errorCode
- if position.Y < 0.f || position.Y > 600.f - 32.f then
- velocity.Y <- -velocity.Y
+ let model =
+ { PhysicsContext = physics
+ CurrentSnapshot = snapshot
+ PlayerId = playerId }
- {
- model with
- Position = position
- Velocity = velocity
- },
- Cmd.none
+ model, Cmd.none
-// ─────────────────────────────────────────────────────────────
-// View
-// ─────────────────────────────────────────────────────────────
+let update (msg: Msg) (model: Model) : struct (Model * Cmd<Msg>) =
+ match msg with
+ | PhysicsTick delta ->
+ let next = LibDjup.physics_context_get_snapshot model.PhysicsContext
+
+ match LibDjup.physics_tick (model.CurrentSnapshot, double delta, next) with
+ | 0 ->
+ LibDjup.physics_context_return_snapshot (model.PhysicsContext, model.CurrentSnapshot)
+ { model with CurrentSnapshot = next }, Cmd.none
+ | errorCode ->
+ printfn "Physics tick failed, code %d" errorCode
+ LibDjup.physics_context_return_snapshot (model.PhysicsContext, next)
+ model, Cmd.none
+ | InputChanged actionState ->
+ // TODO: Actions get applied for a long time, a small press causes the
+ // player to slide in the pressed direction for a while
+ let mutable input = 0uy
+ let isHeld action = actionState.Held.Contains action
+
+ if isHeld MoveUp then
+ input <- input ||| byte PlayerInput.Up
+
+ if isHeld MoveDown then
+ input <- input ||| byte PlayerInput.Down
+
+ if isHeld MoveLeft then
+ input <- input ||| byte PlayerInput.Left
+
+ if isHeld MoveRight then
+ input <- input ||| byte PlayerInput.Right
+
+ let ret =
+ LibDjup.snapshot_set_player_input (model.CurrentSnapshot, model.PlayerId, input)
+
+ match ret with
+ | 0 -> ()
+ | errorCode -> printfn "Failed to set player input (error code %d)" errorCode
+
+ model, Cmd.none
+ | Tick _ -> model, Cmd.none
let view (ctx: GameContext) (model: Model) (buffer: RenderBuffer<RenderCmd2D>) =
- // Draw player (using a 1x1 pixel texture if no asset loaded, or load one)
- let pixel =
- Assets.getOrCreate
- "pixel"
- (fun gd ->
- let t = new Texture2D(gd, 1, 1)
- t.SetData([| Color.White |])
- t)
- ctx
-
- Draw2D.sprite
- pixel
- (Rectangle(int model.Position.X, int model.Position.Y, 32, 32))
- |> Draw2D.withColor Color.Red
- |> Draw2D.submit buffer
-
-// ─────────────────────────────────────────────────────────────
-// Program
-// ─────────────────────────────────────────────────────────────
+ // Draw player (using a 1x1 pixel texture if no asset loaded, or load one)
+ let pixel =
+ Assets.getOrCreate
+ "pixel"
+ (fun gd ->
+ let t = new Texture2D(gd, 1, 1)
+ t.SetData [| Color.White |]
+ t)
+ ctx
+
+ let snapshot: Snapshot =
+ let ptr = model.CurrentSnapshot |> NativePtr.ofNativeInt
+ NativePtr.get ptr 0
+
+ // TODO: Snapshot.Players should be active players
+ for p in snapshot.Players do
+ Draw2D.sprite pixel (Rectangle(int p.Position.X, int p.Position.Y, 32, 32))
+ |> Draw2D.withColor Color.Red
+ |> Draw2D.submit buffer
+
[<EntryPoint>]
let main _ =
- let program =
- Program.mkProgram init update
- |> Program.withAssets
- |> Program.withRenderer(fun g -> Batch2DRenderer.create g view)
- |> Program.withInput
- |> Program.withSubscription(fun ctx _ ->
- InputMapper.subscribeStatic inputMap InputChanged ctx)
- |> Program.withTick Tick
- |> Program.withConfig(fun (game, graphics) ->
- game.Content.RootDirectory <- "Content"
- game.Window.Title <- "Mibo 2D Game"
- game.IsMouseVisible <- true
- graphics.PreferredBackBufferWidth <- 800
- graphics.PreferredBackBufferHeight <- 600)
-
- use game = new ElmishGame<Model, Msg>(program)
- game.Run()
- 0
+ let program =
+ Program.mkProgram init update
+ |> Program.withAssets
+ |> Program.withRenderer (fun g -> Batch2DRenderer.create g view)
+ |> Program.withInput
+ |> Program.withSubscription (fun ctx _ -> InputMapper.subscribeStatic inputMap InputChanged ctx)
+ |> Program.withFixedStep
+ { StepSeconds = 1f / 60f
+ MaxStepsPerFrame = 5
+ MaxFrameSeconds = ValueNone
+ Map = PhysicsTick }
+ |> Program.withTick Tick
+ |> Program.withConfig (fun (game, graphics) ->
+ game.Content.RootDirectory <- "Content"
+ game.Window.Title <- "Djup"
+ game.IsMouseVisible <- true
+ graphics.PreferredBackBufferWidth <- 800
+ graphics.PreferredBackBufferHeight <- 600)
+
+ use game = new ElmishGame<Model, Msg>(program)
+ game.Run()
+ 0
diff --git a/Djup/src/packages.lock.json b/Djup/src/packages.lock.json
index 64ca908..da5e725 100644
--- a/Djup/src/packages.lock.json
+++ b/Djup/src/packages.lock.json
@@ -66,6 +66,9 @@
"type": "Transitive",
"resolved": "0.10.4",
"contentHash": "WYnil3DhQHzjCY0dM9I2B3r1vWip90AOuQd25KE4NrjPQBg0tBJFluRLm5YPnO5ZLDmwrfosY8jCQGQRmWI/Pg=="
+ },
+ "djup.native": {
+ "type": "Project"
}
}
}
diff --git a/lib/physics.c b/lib/physics.c
index adcf433..0039bd7 100644
--- a/lib/physics.c
+++ b/lib/physics.c
@@ -10,8 +10,9 @@
#include "physics.h"
struct context *
-dp_physics_context_create(size_t snapshots)
+dp_physics_context_create(int32_t snapshots, int32_t players, int32_t balls)
{
+ int32_t i, j;
struct context *ctx = NULL;
if (!(ctx = malloc(sizeof(*ctx))))
@@ -26,22 +27,42 @@ dp_physics_context_create(size_t snapshots)
}
memset(ctx->snapshots, 0, sizeof(*ctx->snapshots) * snapshots);
+ for (i = 0; i < snapshots; i++)
+ {
+ if (dp_snapshot_init(&ctx->snapshots[i], players, balls) != 0)
+ {
+ for (j = 0; j < i; j++)
+ {
+ dp_snapshot_deinit(&ctx->snapshots[j]);
+ }
+ free(ctx);
+ }
+ }
+
return ctx;
}
void
dp_physics_context_free(struct context *ctx)
{
+ int32_t i;
if (!ctx)
return;
if (ctx->snapshots)
+ {
+ for (i = 0; i < ctx->snapshots_len; i++)
+ {
+ dp_snapshot_deinit(&ctx->snapshots[i]);
+ }
free(ctx->snapshots);
+ }
free(ctx);
}
-struct snapshot *dp_physics_context_get_snapshot(struct context *ctx)
+struct snapshot *
+dp_physics_context_get_snapshot(struct context *ctx)
{
- size_t i;
+ int32_t i;
struct snapshot *s;
for (i = 0; i < ctx->snapshots_len; i++)
@@ -56,9 +77,10 @@ struct snapshot *dp_physics_context_get_snapshot(struct context *ctx)
return NULL;
}
-void dp_physics_context_return_snapshot(struct context *ctx, struct snapshot *s)
+void
+dp_physics_context_return_snapshot(struct context *ctx, struct snapshot *s)
{
- size_t i;
+ int32_t i;
assert(s->active);
@@ -104,17 +126,17 @@ dp_physics_tick_ball(const struct ball *cur, double delta, struct ball *next)
int
dp_physics_tick(const struct snapshot *cur, double delta, struct snapshot *next)
{
- size_t i;
+ int32_t i;
assert(cur->active);
assert(next->active);
- for (i = 0; i < LENGTH(cur->players); i++)
+ for (i = 0; i < cur->players_len; i++)
{
dp_physics_tick_player(&cur->players[i], delta, &next->players[i]);
}
- for (i = 0; i < LENGTH(cur->balls); i++)
+ for (i = 0; i < cur->balls_len; i++)
{
dp_physics_tick_ball(&cur->balls[i], delta, &next->balls[i]);
}
diff --git a/lib/physics.h b/lib/physics.h
index 7aa2b31..e1c53f3 100644
--- a/lib/physics.h
+++ b/lib/physics.h
@@ -4,11 +4,11 @@ struct snapshot;
/* A game world */
struct context {
- size_t snapshots_len;
+ int32_t snapshots_len;
struct snapshot *snapshots;
};
-struct context *dp_physics_context_create(size_t snapshots);
+struct context *dp_physics_context_create(int32_t snapshots, int32_t players, int32_t balls);
void dp_physics_context_free(struct context *);
struct snapshot *dp_physics_context_get_snapshot(struct context *);
diff --git a/lib/snapshot.c b/lib/snapshot.c
index 88c17a1..d7dde50 100644
--- a/lib/snapshot.c
+++ b/lib/snapshot.c
@@ -1,14 +1,51 @@
+#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "snapshot.h"
-int dp_snapshot_put_player(struct snapshot *s, uint32_t id, const struct player *p)
+int
+dp_snapshot_init(struct snapshot *s, int32_t players_len, int32_t balls_len)
+{
+ if (!s)
+ return -1;
+
+ if (!(s->players = calloc(players_len, sizeof(*s->players))))
+ return -1;
+ if (!(s->balls = calloc(balls_len, sizeof(*s->balls))))
+ {
+ free(s->players);
+ return -1;
+ }
+ s->players_len = players_len;
+ s->balls_len = balls_len;
+ return 0;
+}
+
+void
+dp_snapshot_deinit(struct snapshot *s)
+{
+ if (!s)
+ return;
+ if (s->players)
+ {
+ free(s->players);
+ s->players = NULL;
+ }
+ if (s->balls)
+ {
+ free(s->balls);
+ s->balls = NULL;
+ }
+}
+
+int
+dp_snapshot_put_player(struct snapshot *s, int32_t id, const struct player *p)
{
if (!s || !p)
return -1;
- if (id >= LENGTH(s->players))
+ if (id >= s->players_len)
return -1;
s->players[id] = *p;
@@ -16,13 +53,14 @@ int dp_snapshot_put_player(struct snapshot *s, uint32_t id, const struct player
return 0;
}
-struct player *dp_snapshot_get_player(struct snapshot *s, uint32_t id)
+struct player *
+dp_snapshot_get_player(struct snapshot *s, int32_t id)
{
struct player *p;
if (!s)
return NULL;
- if (id >= LENGTH(s->players))
+ if (id >= s->players_len)
return NULL;
p = &s->players[id];
@@ -31,13 +69,14 @@ struct player *dp_snapshot_get_player(struct snapshot *s, uint32_t id)
return p;
}
-int dp_snapshot_remove_player(struct snapshot *s, uint32_t id)
+int
+dp_snapshot_remove_player(struct snapshot *s, int32_t id)
{
struct player *p;
if (!s)
return -1;
- if (id >= LENGTH(s->players))
+ if (id >= s->players_len)
return -1;
p = dp_snapshot_get_player(s, id);
@@ -46,7 +85,8 @@ int dp_snapshot_remove_player(struct snapshot *s, uint32_t id)
return 0;
}
-int dp_snapshot_set_player_input(struct snapshot *s, uint32_t id, uint8_t input)
+int
+dp_snapshot_set_player_input(struct snapshot *s, int32_t id, uint8_t input)
{
struct player *p;
@@ -56,11 +96,12 @@ int dp_snapshot_set_player_input(struct snapshot *s, uint32_t id, uint8_t input)
return 0;
}
-int dp_snapshot_put_ball(struct snapshot *s, uint32_t id, const struct ball *b)
+int
+dp_snapshot_put_ball(struct snapshot *s, int32_t id, const struct ball *b)
{
if (!s || !b)
return -1;
- if (id >= LENGTH(s->balls))
+ if (id >= s->balls_len)
return -1;
s->balls[id] = *b;
@@ -68,13 +109,14 @@ int dp_snapshot_put_ball(struct snapshot *s, uint32_t id, const struct ball *b)
return 0;
}
-struct ball *dp_snapshot_get_ball(struct snapshot *s, uint32_t id)
+struct ball *
+dp_snapshot_get_ball(struct snapshot *s, int32_t id)
{
struct ball *b;
if (!s)
return NULL;
- if (id >= LENGTH(s->balls))
+ if (id >= s->balls_len)
return NULL;
b = &s->balls[id];
@@ -83,13 +125,14 @@ struct ball *dp_snapshot_get_ball(struct snapshot *s, uint32_t id)
return b;
}
-int dp_snapshot_remove_ball(struct snapshot *s, uint32_t id)
+int
+dp_snapshot_remove_ball(struct snapshot *s, int32_t id)
{
struct ball *b;
if (!s)
return -1;
- if (id >= LENGTH(s->balls))
+ if (id >= s->balls_len)
return -1;
b = dp_snapshot_get_ball(s, id);
diff --git a/lib/snapshot.h b/lib/snapshot.h
index 022d989..028eac2 100644
--- a/lib/snapshot.h
+++ b/lib/snapshot.h
@@ -11,7 +11,6 @@ enum {
struct player {
uint8_t state;
uint8_t input;
- char pad[2];
vec2 pos;
vec2 vel;
};
@@ -25,15 +24,20 @@ struct ball {
struct snapshot {
char active;
uint32_t tick;
- struct player players[WORLD_MAX_PLAYERS];
- struct ball balls[WORLD_MAX_BULLETS];
+ int32_t players_len;
+ struct player *players;
+ int32_t balls_len;
+ struct ball *balls;
};
-int dp_snapshot_put_player(struct snapshot *, uint32_t id, const struct player *);
-struct player *dp_snapshot_get_player(struct snapshot *, uint32_t id);
-int dp_snapshot_remove_player(struct snapshot *, uint32_t id);
-int dp_snapshot_set_player_input(struct snapshot *, uint32_t id, uint8_t input);
+int dp_snapshot_init(struct snapshot *, int32_t players_len, int32_t balls_len);
+void dp_snapshot_deinit(struct snapshot *);
-int dp_snapshot_put_ball(struct snapshot *, uint32_t id, const struct ball *);
-struct ball *dp_snapshot_get_ball(struct snapshot *, uint32_t id);
-int dp_snapshot_remove_ball(struct snapshot *, uint32_t id);
+int dp_snapshot_put_player(struct snapshot *, int32_t id, const struct player *);
+struct player *dp_snapshot_get_player(struct snapshot *, int32_t id);
+int dp_snapshot_remove_player(struct snapshot *, int32_t id);
+int dp_snapshot_set_player_input(struct snapshot *, int32_t id, uint8_t input);
+
+int dp_snapshot_put_ball(struct snapshot *, int32_t id, const struct ball *);
+struct ball *dp_snapshot_get_ball(struct snapshot *, int32_t id);
+int dp_snapshot_remove_ball(struct snapshot *, int32_t id);