diff options
| -rw-r--r-- | Djup.Native/src/LibDjup.cs | 10 | ||||
| -rw-r--r-- | Djup.Native/src/Types.cs | 27 | ||||
| -rw-r--r-- | Djup/src/Djup.fsproj | 4 | ||||
| -rw-r--r-- | Djup/src/Program.fs | 258 | ||||
| -rw-r--r-- | Djup/src/packages.lock.json | 3 | ||||
| -rw-r--r-- | lib/physics.c | 38 | ||||
| -rw-r--r-- | lib/physics.h | 4 | ||||
| -rw-r--r-- | lib/snapshot.c | 69 | ||||
| -rw-r--r-- | lib/snapshot.h | 24 |
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); |
