summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Djup.Native/Constants.cs7
-rw-r--r--Djup.Native/LibDjup.cs27
-rw-r--r--Djup.Native/Types.cs52
-rw-r--r--client/Game.cs37
-rw-r--r--client/Player.cs24
-rw-r--r--client/Root.cs87
-rw-r--r--client/World.cs75
-rw-r--r--client/game.tscn13
-rw-r--r--client/player.tscn6
-rw-r--r--client/project.godot25
-rw-r--r--client/root.tscn6
-rw-r--r--client/world.tscn6
-rwxr-xr-xlib/build.sh4
-rw-r--r--lib/common.h1
-rw-r--r--lib/constants.h12
-rw-r--r--lib/physics.c124
-rw-r--r--lib/physics.h17
-rw-r--r--lib/player.c36
-rw-r--r--lib/player.h5
-rw-r--r--lib/snapshot.c99
-rw-r--r--lib/snapshot.h39
-rw-r--r--lib/types.h9
-rw-r--r--lib/vec2.c7
-rw-r--r--lib/world.c105
-rw-r--r--lib/world.h31
25 files changed, 597 insertions, 257 deletions
diff --git a/Djup.Native/Constants.cs b/Djup.Native/Constants.cs
new file mode 100644
index 0000000..90692be
--- /dev/null
+++ b/Djup.Native/Constants.cs
@@ -0,0 +1,7 @@
+namespace Djup.Native;
+
+public static class Constants
+{
+ public const int WorldMaxPlayers = 5;
+ public const int WorldMaxBullets = 5000;
+}
diff --git a/Djup.Native/LibDjup.cs b/Djup.Native/LibDjup.cs
index 0b4a3db..15dcdad 100644
--- a/Djup.Native/LibDjup.cs
+++ b/Djup.Native/LibDjup.cs
@@ -31,21 +31,24 @@ public static partial class LibDjup
[LibraryImport(LibraryName, EntryPoint = Prefix + nameof(log_set_level))]
public static partial Vec2 log_set_level(LogLevel minLevel);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_create))]
- public static partial IntPtr world_create();
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_create))]
+ public static partial IntPtr physics_context_create(uint snapshots);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_free))]
- public static partial void world_free(IntPtr world);
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_free))]
+ public static partial void physics_context_free(IntPtr context);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_create_entity))]
- public static partial int world_create_entity(IntPtr world, EntityKind kind);
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_get_snapshot))]
+ public static partial IntPtr physics_context_get_snapshot(IntPtr context);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_find_entity))]
- public static unsafe partial Entity* world_find_entity(IntPtr world, int entityId);
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_context_return_snapshot))]
+ public static partial void physics_context_return_snapshot(IntPtr context, IntPtr snapshot);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_remove_entity))]
- public static partial void world_remove_entity(IntPtr world, int entityId);
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(physics_tick))]
+ public static partial int physics_tick(IntPtr currentSnapshot, double delta, IntPtr nextSnapshot);
- [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(world_tick))]
- public static partial int world_tick(IntPtr world, double delta);
+ [LibraryImport(LibraryName, EntryPoint = Prefix + nameof(snapshot_put_player))]
+ public static unsafe partial int snapshot_put_player(IntPtr snapshot, uint 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);
}
diff --git a/Djup.Native/Types.cs b/Djup.Native/Types.cs
index 478208f..7d9c41e 100644
--- a/Djup.Native/Types.cs
+++ b/Djup.Native/Types.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Djup.Native;
@@ -21,29 +22,60 @@ public struct Vec2
public override string ToString() => $"({X}, {Y})";
}
-public enum EntityKind
+public enum EntityState : byte
{
- None = 0,
- Ball
+ Inactive = 0,
+ Active,
+ Removed,
+ Dead
}
-[StructLayout(LayoutKind.Explicit)]
-public struct Entity
+public enum PlayerInput : byte
{
- [FieldOffset(0)]
- public EntityKind kind;
+ Up = 1 << 0,
+ Down = 1 << 1,
+ Left = 1 << 2,
+ Right = 1 << 3
+}
- [FieldOffset(4)]
- public Ball ball;
+[StructLayout(LayoutKind.Sequential)]
+public struct Player
+{
+ public EntityState State { get; }
+ private byte input;
+ public Vec2 Position { get; set; }
+ public Vec2 Velocity { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
-public struct Ball
+public struct Bullet
{
+ public EntityState State { get; set; }
public Vec2 Position { get; set; }
public Vec2 Velocity { get; set; }
}
+[StructLayout(LayoutKind.Sequential)]
+public 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; }
+}
+
public enum LogLevel
{
Trace,
diff --git a/client/Game.cs b/client/Game.cs
new file mode 100644
index 0000000..6bf6192
--- /dev/null
+++ b/client/Game.cs
@@ -0,0 +1,37 @@
+using Godot;
+using System;
+using Djup.Native;
+
+public partial class Game : Node2D
+{
+ private World world;
+
+ private int[] balls = new int[100];
+ private Random rng = new();
+
+ // Called when the node enters the scene tree for the first time.
+ public unsafe override void _Ready()
+ {
+ RenderingServer.SetDefaultClearColor(Colors.LightGray);
+ LibDjup.log_set_output((level, source, line, msg) => {
+ string level_name = level switch
+ {
+ LogLevel.Trace => "trace",
+ LogLevel.Debug => "debug",
+ LogLevel.Info => " info",
+ LogLevel.Warn => " warn",
+ LogLevel.Error => "error",
+ LogLevel.Fatal => "fatal",
+ _ => "UNKNOWN"
+ };
+ GD.Print($"libdjup {source.PadLeft(10)}:{line.ToString().PadRight(3)} {level_name}: {msg}");
+ });
+
+ world = GetNode<World>("World");
+
+ world.PutPlayer(0, new Djup.Native.Player()
+ {
+ Position = new Vec2(100f, 100f),
+ });
+ }
+}
diff --git a/client/Player.cs b/client/Player.cs
new file mode 100644
index 0000000..da95275
--- /dev/null
+++ b/client/Player.cs
@@ -0,0 +1,24 @@
+using Godot;
+using System;
+using Djup.Native;
+
+public partial class Player : Node2D
+{
+ [Export]
+ public World World { get; set; }
+ public uint Id { get; set; } = 0;
+
+ public unsafe override void _Process(double delta)
+ {
+ byte input = 0;
+ if (Input.IsActionPressed("up"))
+ input |= (byte)PlayerInput.Up;
+ if (Input.IsActionPressed("down"))
+ input |= (byte)PlayerInput.Down;
+ if (Input.IsActionPressed("left"))
+ input |= (byte)PlayerInput.Left;
+ if (Input.IsActionPressed("right"))
+ input |= (byte)PlayerInput.Right;
+ World.SetPlayerInput(Id, input);
+ }
+}
diff --git a/client/Root.cs b/client/Root.cs
deleted file mode 100644
index dfbf0da..0000000
--- a/client/Root.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using Godot;
-using System;
-using Djup.Native;
-
-public partial class Root : Node2D
-{
- private IntPtr native_world;
- private int[] balls = new int[100];
- private Random rng = new();
- private const double Tps = 1d/60d;
-
- // Called when the node enters the scene tree for the first time.
- public unsafe override void _Ready()
- {
- RenderingServer.SetDefaultClearColor(Colors.LightGray);
- LibDjup.log_set_output((level, source, line, msg) => {
- string level_name = level switch
- {
- LogLevel.Trace => "trace",
- LogLevel.Debug => "debug",
- LogLevel.Info => "info",
- LogLevel.Warn => "warn",
- LogLevel.Error => "error",
- LogLevel.Fatal => "fatal"
- };
- GD.Print($"libdjup {source}:{line} {level_name.PadLeft(5, ' ')}: {msg}");
- });
-
- native_world = LibDjup.world_create();
- for (int i = 0; i < balls.Length; i++)
- {
- int id = LibDjup.world_create_entity(native_world, EntityKind.Ball);
- if (id < 0)
- {
- throw new Exception("Failed to create entity");
- }
- balls[i] = id;
- Entity *ent;
- var pos = new Vec2(5f * rng.Next(10), 5f * rng.Next(20));
- var vel = LibDjup.vec2_mul(LibDjup.vec2_normal(new Vec2(50f + 5f * rng.Next(10), 50f)), 25.0f);
- GD.Print(vel, " --> ", LibDjup.vec2_length(vel));
- ent = LibDjup.world_find_entity(native_world, balls[i]);
- ent->ball.Position = pos;
- ent->ball.Velocity = vel;
- }
- }
-
- public override void _ExitTree()
- {
- LibDjup.world_free(native_world);
- }
-
- private void DrawBall(Ball ball)
- {
- //Color color = rng.Next(5) switch
- //{
- //0 => Colors.Black,
- //1 => Colors.Blue,
- //2 => Colors.Red,
- //3 => Colors.Green,
- //4 => Colors.Pink
- //};
- Color color = Colors.Blue;
- this.DrawCircle(new Vector2(ball.Position.X, ball.Position.Y), 5f, color, antialiased: true);
- }
-
- public unsafe override void _Draw()
- {
- for (int i = 0; i < balls.Length; i++)
- {
- DrawBall(LibDjup.world_find_entity(native_world, balls[i])->ball);
- }
- }
-
- public override void _Process(double delta)
- {
- this.QueueRedraw();
- }
-
- public override void _PhysicsProcess(double delta)
- {
- if (LibDjup.world_tick(native_world, Tps) != 0)
- {
- GD.Print("world_tick failed");
- }
- }
-}
diff --git a/client/World.cs b/client/World.cs
new file mode 100644
index 0000000..8f5dfb6
--- /dev/null
+++ b/client/World.cs
@@ -0,0 +1,75 @@
+using Godot;
+using System;
+using Djup.Native;
+
+public partial class World : Node2D
+{
+ private IntPtr context;
+ private IntPtr currentSnapshot;
+ public const double Tps = 1d/60d;
+
+ public unsafe override void _Ready()
+ {
+ context = LibDjup.physics_context_create(2);
+ currentSnapshot = LibDjup.physics_context_get_snapshot(context);
+ }
+
+ public override void _ExitTree()
+ {
+ LibDjup.physics_context_free(context);
+ }
+
+ public unsafe void PutPlayer(uint id, Djup.Native.Player player)
+ {
+ if (LibDjup.snapshot_put_player(currentSnapshot, id, &player) != 0)
+ {
+ throw new Exception($"Failed to put player {id}");
+ }
+ }
+
+ public void SetPlayerInput(uint playerId, byte input)
+ {
+ int ret = LibDjup.snapshot_set_player_input(currentSnapshot, playerId, input);
+ if (ret != 0)
+ {
+ throw new Exception($"Failed to set input of player {playerId}: {ret}");
+ }
+ }
+
+ private unsafe void DrawPlayer(Djup.Native.Player player)
+ {
+ float radius = 50f;
+ this.DrawCircle(new Vector2(player.Position.X, player.Position.Y), radius, Colors.DarkBlue, antialiased: true);
+ this.DrawCircle(new Vector2(player.Position.X, player.Position.Y), radius * 0.90f, Colors.Blue, antialiased: true);
+ }
+
+ public unsafe override void _Draw()
+ {
+ Snapshot *s = (Snapshot *)currentSnapshot;
+
+ for (int i = 0; i < Constants.WorldMaxPlayers; i++)
+ {
+ var p = s->Players[i];
+ if (p.State == EntityState.Active)
+ {
+ DrawPlayer(p);
+ }
+ }
+ }
+
+ public override void _Process(double delta)
+ {
+ this.QueueRedraw();
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ IntPtr next = LibDjup.physics_context_get_snapshot(context);
+ if (LibDjup.physics_tick(currentSnapshot, Tps, next) != 0)
+ {
+ throw new Exception("World tick failed");
+ }
+ LibDjup.physics_context_return_snapshot(context, currentSnapshot);
+ currentSnapshot = next;
+ }
+}
diff --git a/client/game.tscn b/client/game.tscn
new file mode 100644
index 0000000..4ec7809
--- /dev/null
+++ b/client/game.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=4 format=3 uid="uid://w0k2j13hm04i"]
+
+[ext_resource type="Script" path="res://Game.cs" id="1_t8lam"]
+[ext_resource type="PackedScene" uid="uid://drdk1by1fun2q" path="res://world.tscn" id="2_tm4sj"]
+[ext_resource type="PackedScene" uid="uid://b0f23dtef1o4h" path="res://player.tscn" id="3_caccr"]
+
+[node name="Game" type="Node2D"]
+script = ExtResource("1_t8lam")
+
+[node name="World" parent="." instance=ExtResource("2_tm4sj")]
+
+[node name="Player" parent="." node_paths=PackedStringArray("World") instance=ExtResource("3_caccr")]
+World = NodePath("../World")
diff --git a/client/player.tscn b/client/player.tscn
new file mode 100644
index 0000000..b1f9f5f
--- /dev/null
+++ b/client/player.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://b0f23dtef1o4h"]
+
+[ext_resource type="Script" path="res://Player.cs" id="1_bjul0"]
+
+[node name="Player" type="Node2D"]
+script = ExtResource("1_bjul0")
diff --git a/client/project.godot b/client/project.godot
index 6107d87..403aac4 100644
--- a/client/project.godot
+++ b/client/project.godot
@@ -11,7 +11,7 @@ config_version=5
[application]
config/name="Djup"
-run/main_scene="res://root.tscn"
+run/main_scene="res://game.tscn"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
config/icon="res://icon.svg"
@@ -22,3 +22,26 @@ window/stretch/mode="canvas_items"
[dotnet]
project/assembly_name="Djup"
+
+[input]
+
+left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
+]
+}
+right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
+]
+}
+up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
+]
+}
+down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
+]
+}
diff --git a/client/root.tscn b/client/root.tscn
deleted file mode 100644
index ded95a0..0000000
--- a/client/root.tscn
+++ /dev/null
@@ -1,6 +0,0 @@
-[gd_scene load_steps=2 format=3 uid="uid://drdk1by1fun2q"]
-
-[ext_resource type="Script" path="res://Root.cs" id="1_p5oo0"]
-
-[node name="Root" type="Node2D"]
-script = ExtResource("1_p5oo0")
diff --git a/client/world.tscn b/client/world.tscn
new file mode 100644
index 0000000..0055d3b
--- /dev/null
+++ b/client/world.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://drdk1by1fun2q"]
+
+[ext_resource type="Script" path="res://World.cs" id="1_7wii1"]
+
+[node name="World" type="Node2D"]
+script = ExtResource("1_7wii1")
diff --git a/lib/build.sh b/lib/build.sh
index f305939..3f8b5a6 100755
--- a/lib/build.sh
+++ b/lib/build.sh
@@ -6,9 +6,9 @@ fail() {
}
CC=cc
-CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -D_POSIX_C_SOURCE=200809L"
+CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Og -g -D_POSIX_C_SOURCE=200809L"
LDFLAGS="-fPIC -lm"
-SOURCES="test.c vec2.c world.c log.c"
+SOURCES="test.c vec2.c snapshot.c log.c player.c physics.c"
output="build"
for arg ; do
diff --git a/lib/common.h b/lib/common.h
new file mode 100644
index 0000000..d6d8816
--- /dev/null
+++ b/lib/common.h
@@ -0,0 +1 @@
+#define LENGTH(x) (sizeof(x) / sizeof(x[0]))
diff --git a/lib/constants.h b/lib/constants.h
new file mode 100644
index 0000000..817fa43
--- /dev/null
+++ b/lib/constants.h
@@ -0,0 +1,12 @@
+#define WORLD_MAX_PLAYERS 5
+#define WORLD_MAX_BULLETS 5000
+
+#define PLAYER_ACCELERATION 150.0f
+#define PLAYER_DRAG 2.0f
+
+enum {
+ INPUT_UP = 1 << 0,
+ INPUT_DOWN = 1 << 1,
+ INPUT_LEFT = 1 << 2,
+ INPUT_RIGHT = 1 << 3
+};
diff --git a/lib/physics.c b/lib/physics.c
new file mode 100644
index 0000000..adcf433
--- /dev/null
+++ b/lib/physics.c
@@ -0,0 +1,124 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "player.h"
+#include "snapshot.h"
+#include "types.h"
+
+#include "physics.h"
+
+struct context *
+dp_physics_context_create(size_t snapshots)
+{
+ struct context *ctx = NULL;
+
+ if (!(ctx = malloc(sizeof(*ctx))))
+ return NULL;
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->snapshots_len = snapshots;
+ if (!(ctx->snapshots = malloc(sizeof(*ctx->snapshots) * snapshots)))
+ {
+ free(ctx);
+ return NULL;
+ }
+ memset(ctx->snapshots, 0, sizeof(*ctx->snapshots) * snapshots);
+
+ return ctx;
+}
+
+void
+dp_physics_context_free(struct context *ctx)
+{
+ if (!ctx)
+ return;
+ if (ctx->snapshots)
+ free(ctx->snapshots);
+ free(ctx);
+}
+
+struct snapshot *dp_physics_context_get_snapshot(struct context *ctx)
+{
+ size_t i;
+ struct snapshot *s;
+
+ for (i = 0; i < ctx->snapshots_len; i++)
+ {
+ s = &ctx->snapshots[i];
+ if (!s->active)
+ {
+ s->active = true;
+ return s;
+ }
+ }
+ return NULL;
+}
+
+void dp_physics_context_return_snapshot(struct context *ctx, struct snapshot *s)
+{
+ size_t i;
+
+ assert(s->active);
+
+ for (i = 0; i < ctx->snapshots_len; i++)
+ {
+ if (s == &ctx->snapshots[i])
+ {
+ s->active = false;
+ }
+ }
+
+ assert(!s->active);
+}
+
+static void
+dp_physics_tick_player(const struct player *cur, double delta, struct player *next)
+{
+ vec2 acc;
+
+ next->state = cur->state;
+ if (next->state == STATE_REMOVED)
+ next->state = STATE_INACTIVE;
+ if (next->state == STATE_INACTIVE)
+ return;
+
+ acc = dp_player_calculate_acceleration(cur);
+ next->vel = dp_vec2_add(cur->vel, dp_vec2_mul(acc, delta));
+ next->pos = dp_vec2_add(cur->pos, dp_vec2_mul(next->vel, delta));
+}
+
+static void
+dp_physics_tick_ball(const struct ball *cur, double delta, struct ball *next)
+{
+ next->state = cur->state;
+ if (next->state == STATE_REMOVED)
+ next->state = STATE_INACTIVE;
+ if (next->state == STATE_INACTIVE)
+ return;
+
+ next->pos = dp_vec2_add(cur->pos, dp_vec2_mul(cur->vel, delta));
+}
+
+int
+dp_physics_tick(const struct snapshot *cur, double delta, struct snapshot *next)
+{
+ size_t i;
+
+ assert(cur->active);
+ assert(next->active);
+
+ for (i = 0; i < LENGTH(cur->players); i++)
+ {
+ dp_physics_tick_player(&cur->players[i], delta, &next->players[i]);
+ }
+
+ for (i = 0; i < LENGTH(cur->balls); i++)
+ {
+ dp_physics_tick_ball(&cur->balls[i], delta, &next->balls[i]);
+ }
+
+ next->tick = cur->tick + 1;
+ return 0;
+}
diff --git a/lib/physics.h b/lib/physics.h
new file mode 100644
index 0000000..7aa2b31
--- /dev/null
+++ b/lib/physics.h
@@ -0,0 +1,17 @@
+#include "types.h"
+
+struct snapshot;
+
+/* A game world */
+struct context {
+ size_t snapshots_len;
+ struct snapshot *snapshots;
+};
+
+struct context *dp_physics_context_create(size_t snapshots);
+void dp_physics_context_free(struct context *);
+
+struct snapshot *dp_physics_context_get_snapshot(struct context *);
+void dp_physics_context_return_snapshot(struct context *, struct snapshot *);
+
+int dp_physics_tick(const struct snapshot *current, double delta, struct snapshot *next);
diff --git a/lib/player.c b/lib/player.c
new file mode 100644
index 0000000..35c3b80
--- /dev/null
+++ b/lib/player.c
@@ -0,0 +1,36 @@
+#include "snapshot.h"
+
+#include "player.h"
+
+vec2
+dp_player_calculate_acceleration(const struct player *p)
+{
+ vec2 acc, dec;
+ int x = 0, y = 0;
+
+ if (p->input & INPUT_UP)
+ {
+ y -= 1;
+ }
+ if (p->input & INPUT_DOWN)
+ {
+ y += 1;
+ }
+
+ if (p->input & INPUT_LEFT)
+ {
+ x -= 1;
+ }
+ if (p->input & INPUT_RIGHT)
+ {
+ x += 1;
+ }
+
+ acc = dp_vec2_new((float)x, (float)y);
+ acc = dp_vec2_normal(acc);
+ acc = dp_vec2_mul(acc, PLAYER_ACCELERATION);
+
+ dec = dp_vec2_mul(p->vel, -1.0f * PLAYER_DRAG);
+
+ return dp_vec2_add(acc, dec);
+}
diff --git a/lib/player.h b/lib/player.h
new file mode 100644
index 0000000..2f365bf
--- /dev/null
+++ b/lib/player.h
@@ -0,0 +1,5 @@
+#include "types.h"
+
+struct player;
+
+vec2 dp_player_calculate_acceleration(const struct player *);
diff --git a/lib/snapshot.c b/lib/snapshot.c
new file mode 100644
index 0000000..88c17a1
--- /dev/null
+++ b/lib/snapshot.c
@@ -0,0 +1,99 @@
+#include <string.h>
+
+#include "common.h"
+
+#include "snapshot.h"
+
+int dp_snapshot_put_player(struct snapshot *s, uint32_t id, const struct player *p)
+{
+ if (!s || !p)
+ return -1;
+ if (id >= LENGTH(s->players))
+ return -1;
+
+ s->players[id] = *p;
+ s->players[id].state = STATE_ACTIVE;
+ return 0;
+}
+
+struct player *dp_snapshot_get_player(struct snapshot *s, uint32_t id)
+{
+ struct player *p;
+
+ if (!s)
+ return NULL;
+ if (id >= LENGTH(s->players))
+ return NULL;
+
+ p = &s->players[id];
+ if (p->state == STATE_INACTIVE)
+ return NULL;
+ return p;
+}
+
+int dp_snapshot_remove_player(struct snapshot *s, uint32_t id)
+{
+ struct player *p;
+
+ if (!s)
+ return -1;
+ if (id >= LENGTH(s->players))
+ return -1;
+
+ p = dp_snapshot_get_player(s, id);
+ memset(p, 0, sizeof(*p));
+ p->state = STATE_REMOVED;
+ return 0;
+}
+
+int dp_snapshot_set_player_input(struct snapshot *s, uint32_t id, uint8_t input)
+{
+ struct player *p;
+
+ if (!(p = dp_snapshot_get_player(s, id)))
+ return -1;
+ p->input = input;
+ return 0;
+}
+
+int dp_snapshot_put_ball(struct snapshot *s, uint32_t id, const struct ball *b)
+{
+ if (!s || !b)
+ return -1;
+ if (id >= LENGTH(s->balls))
+ return -1;
+
+ s->balls[id] = *b;
+ s->balls[id].state = STATE_ACTIVE;
+ return 0;
+}
+
+struct ball *dp_snapshot_get_ball(struct snapshot *s, uint32_t id)
+{
+ struct ball *b;
+
+ if (!s)
+ return NULL;
+ if (id >= LENGTH(s->balls))
+ return NULL;
+
+ b = &s->balls[id];
+ if (b->state == STATE_INACTIVE)
+ return NULL;
+ return b;
+}
+
+int dp_snapshot_remove_ball(struct snapshot *s, uint32_t id)
+{
+ struct ball *b;
+
+ if (!s)
+ return -1;
+ if (id >= LENGTH(s->balls))
+ return -1;
+
+ b = dp_snapshot_get_ball(s, id);
+ memset(b, 0, sizeof(*b));
+ b->state = STATE_REMOVED;
+ return 0;
+}
diff --git a/lib/snapshot.h b/lib/snapshot.h
new file mode 100644
index 0000000..022d989
--- /dev/null
+++ b/lib/snapshot.h
@@ -0,0 +1,39 @@
+#include "types.h"
+#include "constants.h"
+
+enum {
+ STATE_INACTIVE = 0,
+ STATE_ACTIVE,
+ STATE_REMOVED,
+ STATE_DEAD
+};
+
+struct player {
+ uint8_t state;
+ uint8_t input;
+ char pad[2];
+ vec2 pos;
+ vec2 vel;
+};
+
+struct ball {
+ uint8_t state;
+ vec2 pos;
+ vec2 vel;
+};
+
+struct snapshot {
+ char active;
+ uint32_t tick;
+ struct player players[WORLD_MAX_PLAYERS];
+ struct ball balls[WORLD_MAX_BULLETS];
+};
+
+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_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);
diff --git a/lib/types.h b/lib/types.h
index 8618df9..bf42576 100644
--- a/lib/types.h
+++ b/lib/types.h
@@ -1,3 +1,10 @@
+#ifndef _TYPES_H
+#define _TYPES_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
typedef struct {
float x;
float y;
@@ -10,3 +17,5 @@ vec2 dp_vec2_mul(vec2, float scalar);
vec2 dp_vec2_dot(vec2, vec2);
float dp_vec2_length(vec2);
vec2 dp_vec2_normal(vec2);
+
+#endif
diff --git a/lib/vec2.c b/lib/vec2.c
index d795d50..55f5078 100644
--- a/lib/vec2.c
+++ b/lib/vec2.c
@@ -61,7 +61,8 @@ dp_vec2_length(vec2 v)
vec2
dp_vec2_normal(vec2 v)
{
- float a;
- a = 1.f / dp_vec2_length(v);
- return dp_vec2_mul(v, a);
+ float len = dp_vec2_length(v);
+ if (len == 0.0f)
+ return dp_vec2_new(0.0f, 0.0f);
+ return dp_vec2_mul(v, 1.0f / len);
}
diff --git a/lib/world.c b/lib/world.c
deleted file mode 100644
index 34d7489..0000000
--- a/lib/world.c
+++ /dev/null
@@ -1,105 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-
-#include "types.h"
-#include "world.h"
-#include "log.h"
-
-struct world {
- struct entity entities[WORLD_MAX_ENTITIES];
-};
-
-struct world *
-dp_world_create()
-{
- struct world *w;
-
- if (!(w = malloc(sizeof(*w))))
- return NULL;
- memset(w, 0, sizeof(*w));
-
- return w;
-}
-
-void
-dp_free_world(struct world *w)
-{
- if (w)
- free(w);
-}
-
-int
-dp_world_create_entity(struct world *w, int kind)
-{
- int i;
- struct entity *e;
-
- if (!w)
- return -1;
- if (kind == ENTITY_NONE)
- return -1;
-
- for (i = 0; i < WORLD_MAX_ENTITIES; i++) {
- e = &w->entities[i];
- if (e->kind != ENTITY_NONE)
- continue;
- e->kind = kind;
- return i;
- }
-
- return -1;
-}
-
-struct entity *
-dp_world_find_entity(struct world *w, int entity_id)
-{
- struct entity *e;
-
- if (!w || entity_id < 0)
- return NULL;
- if (entity_id >= WORLD_MAX_ENTITIES)
- return NULL;
-
- e = &w->entities[entity_id];
-
- if (e->kind == ENTITY_NONE)
- return NULL;
-
- return e;
-}
-
-int
-dp_world_remove_entity(struct world *w, int entity_id)
-{
- struct entity *e;
-
- if (!(e = dp_world_find_entity(w, entity_id)))
- return -1;
-
- memset(e, 0, sizeof(*e));
- e->kind = ENTITY_NONE;
- return 0;
-}
-
-int
-dp_world_tick(struct world *w, double delta)
-{
- int i;
- struct entity *e;
- struct entity_ball *ball;
-
- for (i = 0; i < WORLD_MAX_ENTITIES; i++)
- {
- e = &w->entities[i];
- switch (e->kind) {
- case ENTITY_BALL:
- ball = &e->e.ball;
- /*ball->pos = dp_vec2_new(500.0, 500.0);*/
- ball->pos = dp_vec2_add(ball->pos, dp_vec2_mul(ball->vel, delta));
- break;
- default:
- break;
- }
- }
- return 0;
-}
diff --git a/lib/world.h b/lib/world.h
deleted file mode 100644
index 40c3fef..0000000
--- a/lib/world.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#define WORLD_MAX_ENTITIES 5000
-
-enum {
- ENTITY_NONE,
- ENTITY_BALL
-};
-
-struct entity_ball {
- vec2 pos;
- vec2 vel;
-};
-
-union entity_union {
- struct entity_ball ball;
-};
-
-struct entity {
- int kind;
- union entity_union e;
-};
-
-struct world;
-
-struct world *dp_world_create();
-void dp_world_free(struct world *);
-
-int dp_world_create_entity(struct world *, int kind);
-struct entity *dp_world_find_entity(struct world *, int entity_id);
-int dp_world_remove_entity(struct world *, int entity_id);
-
-int dp_world_tick(struct world *, double delta);