remove unused layers
This commit is contained in:
parent
9d4e74f0b6
commit
2561d162f5
@ -5,7 +5,7 @@ Run `build.bat` from a terminal with MSVC/Clang available.
|
||||
### Codebase structure
|
||||
---
|
||||
|
||||
This codebase is organized into a series of layers with acyclic dependencies, denoted by the file tree and by namespacing in source code. The `.lay` files that exist alongside each layer specify these dependencies along with things like source files, CPU/GPU linkage information, and binary assets to embed with the layer. These files are parsed by the metaprogram (controlled by build.bat) in order to assemble the final executable. The base layer is the only layer that doesn't adhere to these rules, so that the metaprogram can itself depend on it.
|
||||
This codebase is organized into a series of layers with acyclic dependencies, denoted by the file tree and by namespacing in source code. The `.lay` files that exist alongside each layer specify these dependencies along with things like source files, CPU/GPU linkage information, and binary assets to embed with the layer. These files are parsed by the metaprogram in order to assemble the final executable. The base layer is the only layer that doesn't adhere to these rules, so that the metaprogram can itself depend on it.
|
||||
|
||||
Other than `.lay` files, there are three types of source files present:
|
||||
* `.c`/`.h` C code
|
||||
@ -14,4 +14,4 @@ Other than `.lay` files, there are three types of source files present:
|
||||
|
||||
### Media
|
||||
---
|
||||
For project media, see https://projects.cagori.com/#power-play
|
||||
For project media, see https://projects.cagori.com/#power-play
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,217 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Tweakable defines
|
||||
|
||||
// How close can non-overlapping shapes be before collision is considered
|
||||
#define CLD_CollisionTolerance 0.005f
|
||||
|
||||
// NOTE: Should always be less than tolerance, since colliding = 1 if origin is within this distance.
|
||||
#define CLD_MinUniquePtDistSq (0.001f * 0.001f)
|
||||
|
||||
// To prevent extremely large prototypes when origin is in exact center of rounded feature
|
||||
#define CLD_MaxEpaIterations 64
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Shape types
|
||||
|
||||
Struct(CLD_Shape)
|
||||
{
|
||||
Vec2 points[8];
|
||||
u32 count;
|
||||
f32 radius;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Menkowski types
|
||||
|
||||
Struct(CLD_SupportPoint)
|
||||
{
|
||||
Vec2 p;
|
||||
u32 i; // Index of original point in shape
|
||||
};
|
||||
|
||||
Struct(CLD_MenkowskiPoint)
|
||||
{
|
||||
Vec2 p; // Menkowski difference point
|
||||
CLD_SupportPoint s0; // Support point of first shape in dir
|
||||
CLD_SupportPoint s1; // Support point of second shape in -dir
|
||||
};
|
||||
|
||||
Struct(CLD_MenkowskiSimplex)
|
||||
{
|
||||
u32 len;
|
||||
CLD_MenkowskiPoint a, b, c;
|
||||
};
|
||||
|
||||
Struct(CLD_MenkowskiFeature)
|
||||
{
|
||||
u32 len;
|
||||
CLD_MenkowskiPoint a, b;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Collision types
|
||||
|
||||
Struct(CLD_CollisionPoint)
|
||||
{
|
||||
Vec2 point;
|
||||
f32 separation;
|
||||
u32 id; // Based on polygon edge-to-edge
|
||||
};
|
||||
|
||||
Struct(CLD_Prototype)
|
||||
{
|
||||
Vec2 points[64];
|
||||
u32 len;
|
||||
};
|
||||
|
||||
Struct(CLD_CollisionData)
|
||||
{
|
||||
Vec2 normal;
|
||||
CLD_CollisionPoint points[2];
|
||||
u32 num_points;
|
||||
|
||||
// For debugging
|
||||
b32 solved;
|
||||
CLD_MenkowskiSimplex simplex;
|
||||
CLD_Prototype prototype;
|
||||
|
||||
// For debugging
|
||||
Vec2 a0, b0, a1, b1;
|
||||
Vec2 a0_clipped, b0_clipped, a1_clipped, b1_clipped;
|
||||
};
|
||||
|
||||
Struct(CLD_ClosestPointData)
|
||||
{
|
||||
Vec2 p0, p1;
|
||||
b32 colliding;
|
||||
|
||||
// For debugging
|
||||
b32 solved;
|
||||
CLD_MenkowskiSimplex simplex;
|
||||
CLD_Prototype prototype;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Clipping types
|
||||
|
||||
Struct(CLD_ClippedLine)
|
||||
{
|
||||
Vec2 a0_clipped, b0_clipped;
|
||||
Vec2 a1_clipped, b1_clipped;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Gjk types
|
||||
|
||||
Struct(CLD_GjkData)
|
||||
{
|
||||
CLD_MenkowskiSimplex simplex;
|
||||
Vec2 final_dir;
|
||||
|
||||
// If 1, simplex represents triangle inside of menkowski difference
|
||||
// encapsulating the origin. If 0, simplex represents the closest
|
||||
// feature on menkowski difference to the origin.
|
||||
b32 overlapping;
|
||||
|
||||
#if COLLIDER_DEBUG
|
||||
u32 dbg_step;
|
||||
#endif
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Epa types
|
||||
|
||||
Struct(CLD_EpaData)
|
||||
{
|
||||
Vec2 normal;
|
||||
CLD_MenkowskiFeature closest_feature; // Represents closest feature (edge or point) to origin on menkowski difference
|
||||
|
||||
#if COLLIDER_DEBUG
|
||||
CLD_Prototype prototype;
|
||||
u32 dbg_step;
|
||||
#endif
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Debug helpers
|
||||
|
||||
#if COLLIDER_DEBUG
|
||||
void CLD_DebugBreakable(void);
|
||||
|
||||
#define CLD_DBGSTEP \
|
||||
dbg_step++; \
|
||||
if (dbg_step >= GetGstat(DebugSteps)) \
|
||||
{ \
|
||||
goto abort; \
|
||||
} \
|
||||
else if (dbg_step >= GetGstat(DebugSteps) - 1) \
|
||||
{ \
|
||||
CLD_DebugBreakable(); \
|
||||
} (void)0
|
||||
#else
|
||||
#define CLD_DBGSTEP
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Shape
|
||||
|
||||
CLD_Shape CLD_ShapeFromQuad(Quad quad);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Menkowski
|
||||
|
||||
CLD_SupportPoint CLD_SupportPointFromDirEx(CLD_Shape *shape, Affine af, Vec2 dir, i32 ignore);
|
||||
CLD_SupportPoint CLD_SupportPointFromDir(CLD_Shape *shape, Affine af, Vec2 dir);
|
||||
CLD_MenkowskiPoint CLD_MenkowskiPointFromDir(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, Vec2 dir);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Aabb
|
||||
|
||||
Aabb CLD_AabbFromShape(CLD_Shape *shape, Affine af);
|
||||
Aabb CLD_CombineAabb(Aabb b0, Aabb b1);
|
||||
b32 CLD_TestAabb(Aabb box0, Aabb box1);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Gjk
|
||||
|
||||
#if COLLIDER_DEBUG
|
||||
CLD_GjkData CLD_GjkDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, f32 min_unique_pt_dist_sq, u32 dbg_step);
|
||||
#else
|
||||
CLD_GjkData CLD_GjkDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, f32 min_unique_pt_dist_sq);
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Epa
|
||||
|
||||
#if COLLIDER_DEBUG
|
||||
CLD_EpaData CLD_EpaDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, CLD_GjkData gjk_result, f32 min_unique_pt_dist_sq, u32 max_iterations, u32 dbg_step);
|
||||
#else
|
||||
CLD_EpaData CLD_EpaDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, CLD_GjkData gjk_result, f32 min_unique_pt_dist_sq, u32 max_iterations);
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Clipping
|
||||
|
||||
CLD_ClippedLine CLD_ClipLineToLine(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1, Vec2 normal);
|
||||
Vec2 CLD_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Collision point
|
||||
|
||||
CLD_CollisionData CLD_CollisionDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Closest point
|
||||
|
||||
CLD_ClosestPointData CLD_ClosestPointDataFromShapes(CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Time of impact
|
||||
|
||||
f32 CLD_TimeOfImpact(CLD_Shape *c0, CLD_Shape *c1, Affine af0_t0, Affine af1_t0, Affine af0_t1, Affine af1_t1, f32 tolerance, u32 max_iterations);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Point cloud debug
|
||||
|
||||
Vec2Array CLD_Menkowski(Arena *arena, CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1, u32 detail);
|
||||
Vec2Array CLD_PointCloud(Arena *arena, CLD_Shape *shape0, CLD_Shape *shape1, Affine af0, Affine af1);
|
||||
@ -1,11 +0,0 @@
|
||||
@Layer collider
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC collider.h
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@IncludeC collider.c
|
||||
468
src/draw/draw.c
468
src/draw/draw.c
@ -1,468 +0,0 @@
|
||||
D_Ctx D = Zi;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Bootstrap
|
||||
|
||||
void D_Bootstrap(void)
|
||||
{
|
||||
u32 pixel_white = 0xFFFFFFFF;
|
||||
D.solid_white_texture = GPU_AcquireTexture(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, VEC2I32(1, 1), &pixel_white);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Material
|
||||
|
||||
void D_DrawMaterial(GPU_RenderSig *sig, D_MaterialParams params)
|
||||
{
|
||||
GPU_RenderCmdDesc cmd = ZI;
|
||||
cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL;
|
||||
cmd.material.af = params.af;
|
||||
cmd.material.texture = params.texture;
|
||||
cmd.material.clip = params.clip;
|
||||
cmd.material.tint = params.tint;
|
||||
cmd.material.is_light = params.is_light;
|
||||
cmd.material.light_emittance = params.light_emittance;
|
||||
GPU_PushRenderCmd(sig, &cmd);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Solid shapes
|
||||
|
||||
void D_DrawPolyEx(GPU_RenderSig *sig, Vec2Array vertices, GPU_Indices indices, u32 color)
|
||||
{
|
||||
GPU_RenderCmdDesc cmd = ZI;
|
||||
cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_SHAPE;
|
||||
cmd.ui_shape.vertices = vertices;
|
||||
cmd.ui_shape.indices = indices;
|
||||
cmd.ui_shape.color = color;
|
||||
GPU_PushRenderCmd(sig, &cmd);
|
||||
}
|
||||
|
||||
// Draws a filled polygon using triangles in a fan pattern
|
||||
void D_DrawPoly(GPU_RenderSig *sig, Vec2Array vertices, u32 color)
|
||||
{
|
||||
if (vertices.count >= 3)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
|
||||
u32 num_tris = vertices.count - 2;
|
||||
u32 num_indices = num_tris * 3;
|
||||
|
||||
// Generate indices in a fan pattern
|
||||
GPU_Indices indices = ZI;
|
||||
indices.count = num_indices;
|
||||
indices.indices = PushStructsNoZero(scratch.arena, u32, num_indices);
|
||||
for (u32 i = 0; i < num_tris; ++i)
|
||||
{
|
||||
u32 tri_offset = i * 3;
|
||||
indices.indices[tri_offset + 0] = 0;
|
||||
indices.indices[tri_offset + 1] = (i + 1);
|
||||
indices.indices[tri_offset + 2] = (i + 2);
|
||||
}
|
||||
|
||||
D_DrawPolyEx(sig, vertices, indices, color);
|
||||
|
||||
EndScratch(scratch);
|
||||
}
|
||||
}
|
||||
|
||||
void D_DrawCircle(GPU_RenderSig *sig, Vec2 pos, f32 radius, u32 color, u32 detail)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
|
||||
Vec2 *points = PushStructsNoZero(scratch.arena, Vec2, detail);
|
||||
for (u32 i = 0; i < detail; ++i)
|
||||
{
|
||||
f32 angle = ((f32)i / (f32)detail) * Tau;
|
||||
Vec2 p = VEC2(
|
||||
radius * CosF32(angle),
|
||||
radius * SinF32(angle)
|
||||
);
|
||||
points[i] = AddVec2(pos, p);
|
||||
}
|
||||
|
||||
Vec2Array vertices = {
|
||||
.points = points,
|
||||
.count = detail
|
||||
};
|
||||
D_DrawPoly(sig, vertices, color);
|
||||
|
||||
EndScratch(scratch);
|
||||
}
|
||||
|
||||
void D_DrawQuad(GPU_RenderSig *sig, Quad quad, u32 color)
|
||||
{
|
||||
PERSIST const u32 indices_array[6] = {
|
||||
0, 1, 2,
|
||||
0, 2, 3
|
||||
};
|
||||
Vec2Array vertices = { .count = 4, .points = quad.e };
|
||||
GPU_Indices indices = { .count = 6, .indices = indices_array };
|
||||
D_DrawPolyEx(sig, vertices, indices, color);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Line shapes
|
||||
|
||||
void D_DrawLineGradient(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 start_color, u32 end_color)
|
||||
{
|
||||
#if 0
|
||||
Quad quad = QuadFromLine(start, end, thickness);
|
||||
D_DrawMaterial(sig, D_MATERIALPARAMS(.texture = D.solid_white_texture, .tint0 = start_color, .tint1 = end_color, .quad = quad));
|
||||
#else
|
||||
// Placeholder
|
||||
Quad quad = QuadFromLine(start, end, thickness);
|
||||
D_DrawQuad(sig, quad, start_color);
|
||||
#endif
|
||||
}
|
||||
|
||||
void D_DrawLine(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 color)
|
||||
{
|
||||
Quad quad = QuadFromLine(start, end, thickness);
|
||||
D_DrawQuad(sig, quad, color);
|
||||
}
|
||||
|
||||
void D_DrawRay(GPU_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, u32 color)
|
||||
{
|
||||
Quad quad = QuadFromRay(pos, rel, thickness);
|
||||
D_DrawQuad(sig, quad, color);
|
||||
}
|
||||
|
||||
void D_DrawPolyLine(GPU_RenderSig *sig, Vec2Array points, b32 loop, f32 thickness, u32 color)
|
||||
{
|
||||
if (points.count >= 2)
|
||||
{
|
||||
for (u64 i = 1; i < points.count; ++i)
|
||||
{
|
||||
Vec2 p1 = points.points[i - 1];
|
||||
Vec2 p2 = points.points[i];
|
||||
Quad q = QuadFromLine(p1, p2, thickness);
|
||||
D_DrawQuad(sig, q, color);
|
||||
}
|
||||
if (loop && points.count > 2)
|
||||
{
|
||||
Vec2 p1 = points.points[points.count - 1];
|
||||
Vec2 p2 = points.points[0];
|
||||
Quad q = QuadFromLine(p1, p2, thickness);
|
||||
D_DrawQuad(sig, q, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void D_DrawCircleLine(GPU_RenderSig *sig, Vec2 pos, f32 radius, f32 thickness, u32 color, u32 detail)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
|
||||
Vec2 *points = PushStructsNoZero(scratch.arena, Vec2, detail);
|
||||
for (u32 i = 0; i < detail; ++i)
|
||||
{
|
||||
f32 angle = ((f32)i / (f32)detail) * Tau;
|
||||
Vec2 p = VEC2(
|
||||
radius * CosF32(angle),
|
||||
radius * SinF32(angle)
|
||||
);
|
||||
points[i] = AddVec2(pos, p);
|
||||
}
|
||||
|
||||
Vec2Array a = {
|
||||
.points = points,
|
||||
.count = detail
|
||||
};
|
||||
D_DrawPolyLine(sig, a, 1, thickness, color);
|
||||
|
||||
EndScratch(scratch);
|
||||
}
|
||||
|
||||
void D_DrawQuadLine(GPU_RenderSig *sig, Quad quad, f32 thickness, u32 color)
|
||||
{
|
||||
Vec2 points[] = { quad.p0, quad.p1, quad.p2, quad.p3 };
|
||||
Vec2Array a = { .points = points, .count = countof(points) };
|
||||
D_DrawPolyLine(sig, a, 1, thickness, color);
|
||||
}
|
||||
|
||||
void D_DrawArrowLine(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, f32 arrowhead_height, u32 color)
|
||||
{
|
||||
const f32 head_width_ratio = 0.5f; // Width of arrowhead relative to its length
|
||||
|
||||
const f32 max_height_to_line_ratio = 0.9f; // Maximum length of arrowhead relative to total line length
|
||||
arrowhead_height = MinF32(arrowhead_height, Vec2Len(SubVec2(end, start)) * max_height_to_line_ratio);
|
||||
|
||||
Vec2 head_start_dir = SubVec2(start, end);
|
||||
head_start_dir = NormVec2(head_start_dir);
|
||||
head_start_dir = MulVec2(head_start_dir, arrowhead_height);
|
||||
|
||||
Vec2 head_start = AddVec2(end, head_start_dir);
|
||||
|
||||
Vec2 head_p1_dir = MulPerpVec2(head_start_dir, head_width_ratio);
|
||||
Vec2 head_p2_dir = NegVec2(head_p1_dir);
|
||||
|
||||
Vec2 head_p1 = AddVec2(head_start, head_p1_dir);
|
||||
Vec2 head_p2 = AddVec2(head_start, head_p2_dir);
|
||||
|
||||
Vec2 head_points[] = { end, head_p1, head_p2 };
|
||||
Vec2Array head_points_v2_array = {
|
||||
.points = head_points,
|
||||
.count = countof(head_points)
|
||||
};
|
||||
D_DrawPoly(sig, head_points_v2_array, color);
|
||||
|
||||
Quad line_quad = QuadFromLine(start, head_start, thickness);
|
||||
D_DrawQuad(sig, line_quad, color);
|
||||
}
|
||||
|
||||
void D_DrawArrowRay(GPU_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, f32 arrowhead_height, u32 color)
|
||||
{
|
||||
Vec2 end = AddVec2(pos, rel);
|
||||
D_DrawArrowLine(sig, pos, end, thickness, arrowhead_height, color);
|
||||
}
|
||||
|
||||
void D_DrawColliderLine(GPU_RenderSig *sig, CLD_Shape shape, Affine shape_af, f32 thickness, u32 color, u32 detail)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
Vec2Array poly = ZI;
|
||||
if (shape.radius == 0)
|
||||
{
|
||||
poly.count = shape.count;
|
||||
poly.points = PushStructsNoZero(scratch.arena, Vec2, shape.count);
|
||||
for (u32 i = 0; i < shape.count; ++i)
|
||||
{
|
||||
Vec2 p = MulAffineVec2(shape_af, shape.points[i]);
|
||||
poly.points[i] = p;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
poly.count = detail;
|
||||
poly.points = PushStructsNoZero(scratch.arena, Vec2, detail);
|
||||
for (u32 i = 0; i < detail; ++i)
|
||||
{
|
||||
f32 angle = ((f32)i / (f32)detail) * Tau;
|
||||
Vec2 dir = VEC2(CosF32(angle), SinF32(angle));
|
||||
Vec2 p = CLD_SupportPointFromDir(&shape, shape_af, dir).p;
|
||||
poly.points[i] = p;
|
||||
}
|
||||
}
|
||||
D_DrawPolyLine(sig, poly, 1, thickness, color);
|
||||
EndScratch(scratch);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Grid
|
||||
|
||||
void D_DrawGrid(GPU_RenderSig *sig, Affine af, u32 bg0_color, u32 bg1_color, u32 line_color, u32 x_color, u32 y_color, f32 thickness, f32 spacing, Vec2 offset)
|
||||
{
|
||||
i32 grid_id = 0;
|
||||
{
|
||||
GPU_RenderCmdDesc cmd = ZI;
|
||||
cmd.kind = GP_RENDER_CMD_KIND_PUSH_GRID;
|
||||
cmd.grid.bg0_color = bg0_color;
|
||||
cmd.grid.bg1_color = bg1_color;
|
||||
cmd.grid.line_color = line_color;
|
||||
cmd.grid.x_color = x_color;
|
||||
cmd.grid.y_color = y_color;
|
||||
cmd.grid.line_thickness = thickness;
|
||||
cmd.grid.line_spacing = spacing;
|
||||
cmd.grid.offset = offset;
|
||||
grid_id = GPU_PushRenderCmd(sig, &cmd);
|
||||
}
|
||||
|
||||
GPU_RenderCmdDesc cmd = ZI;
|
||||
cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL;
|
||||
cmd.material.af = af;
|
||||
cmd.material.tint = Color_White;
|
||||
cmd.material.grid_cmd_id = grid_id;
|
||||
GPU_PushRenderCmd(sig, &cmd);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Ui
|
||||
|
||||
void D_DrawUiRect(GPU_RenderSig *sig, D_UiRectParams params)
|
||||
{
|
||||
GPU_RenderCmdDesc cmd = ZI;
|
||||
cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_RECT;
|
||||
cmd.ui_rect.af = params.af;
|
||||
cmd.ui_rect.texture = params.texture;
|
||||
cmd.ui_rect.clip = params.clip;
|
||||
cmd.ui_rect.tint = params.tint;
|
||||
GPU_PushRenderCmd(sig, &cmd);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Text
|
||||
|
||||
// Returns the rect of the text area
|
||||
Rect draw_text(GPU_RenderSig *sig, D_TextParams params)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
|
||||
f32 inv_font_image_width = 1.0 / (f32)params.font->image_width;
|
||||
f32 inv_font_image_height = 1.0 / (f32)params.font->image_height;
|
||||
f32 line_spacing = params.font->point_size * 1.5f * params.scale;
|
||||
|
||||
//- Build line glyphs
|
||||
|
||||
u64 num_lines = 0;
|
||||
f32 widest_line = 0;
|
||||
|
||||
D_TextLine *first_line = 0;
|
||||
D_TextLine *last_line = 0;
|
||||
f32 first_line_top_offset = 0;
|
||||
f32 last_line_bottom_offset = 0;
|
||||
|
||||
if (params.str.len > 0)
|
||||
{
|
||||
b32 string_done = 0;
|
||||
CodepointIter iter = BeginCodepointIter(params.str);
|
||||
while (!string_done)
|
||||
{
|
||||
f32 line_width = 0;
|
||||
f32 top_offset = 0;
|
||||
f32 bottom_offset = 0;
|
||||
u64 num_line_glyphs = 0;
|
||||
D_TextGlyph *line_glyphs = PushDry(scratch.arena, D_TextGlyph);
|
||||
|
||||
b32 line_done = 0;
|
||||
while (!line_done)
|
||||
{
|
||||
string_done = !AdvanceCodepointIter(&iter);
|
||||
if (string_done)
|
||||
{
|
||||
line_done = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 codepoint = iter.codepoint;
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
line_done = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
D_TextGlyph *tg = PushStruct(scratch.arena, D_TextGlyph);
|
||||
++num_line_glyphs;
|
||||
F_Glyph *glyph = F_GetGlyph(params.font, codepoint);
|
||||
tg->off_x = glyph->off_x * params.scale;
|
||||
tg->off_y = glyph->off_y * params.scale;
|
||||
tg->width = glyph->width * params.scale;
|
||||
tg->height = glyph->height * params.scale;
|
||||
tg->advance = glyph->advance * params.scale;
|
||||
Rect glyph_atlas_rect = glyph->atlas_rect;
|
||||
tg->clip = (ClipRect) {
|
||||
{
|
||||
glyph_atlas_rect.x * inv_font_image_width,
|
||||
glyph_atlas_rect.y * inv_font_image_height
|
||||
},
|
||||
{
|
||||
(glyph_atlas_rect.x + glyph_atlas_rect.width) * inv_font_image_width,
|
||||
(glyph_atlas_rect.y + glyph_atlas_rect.height) * inv_font_image_height
|
||||
}
|
||||
};
|
||||
line_width += tg->advance;
|
||||
top_offset = MinF32(top_offset, tg->off_y);
|
||||
bottom_offset = MaxF32(bottom_offset, tg->off_y + tg->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Line ended
|
||||
// TODO: Only create nodes for non-empty lines. Embed line number in the node.
|
||||
D_TextLine *node = PushStruct(scratch.arena, D_TextLine);
|
||||
node->line_width = line_width;
|
||||
node->num_glyphs = num_line_glyphs;
|
||||
node->glyphs = line_glyphs;
|
||||
if (last_line)
|
||||
{
|
||||
last_line->next = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
first_line = node;
|
||||
first_line_top_offset = top_offset;
|
||||
}
|
||||
last_line = node;
|
||||
last_line_bottom_offset = bottom_offset;
|
||||
widest_line = MaxF32(widest_line, line_width);
|
||||
++num_lines;
|
||||
}
|
||||
EndCodepointIter(&iter);
|
||||
}
|
||||
|
||||
//- Determine text bounds
|
||||
|
||||
Rect bounds = ZI;
|
||||
bounds.x = params.pos.x;
|
||||
bounds.y = params.pos.y;
|
||||
bounds.width = widest_line;
|
||||
bounds.height = num_lines * line_spacing + first_line_top_offset + last_line_bottom_offset;
|
||||
|
||||
// Offset bounds X
|
||||
switch (params.offset_x)
|
||||
{
|
||||
case DRAW_TEXT_OFFSET_X_LEFT: break;
|
||||
case DRAW_TEXT_OFFSET_X_CENTER:
|
||||
{
|
||||
bounds.x -= bounds.width / 2.f;
|
||||
} break;
|
||||
case DRAW_TEXT_OFFSET_X_RIGHT:
|
||||
{
|
||||
bounds.x -= bounds.width;
|
||||
} break;
|
||||
}
|
||||
|
||||
// Offset bounds Y
|
||||
switch (params.offset_y)
|
||||
{
|
||||
case DRAW_TEXT_OFFSET_Y_TOP: break;
|
||||
case DRAW_TEXT_OFFSET_Y_CENTER:
|
||||
{
|
||||
bounds.y -= bounds.height / 2.f;
|
||||
} break;
|
||||
case DRAW_TEXT_OFFSET_Y_BOTTOM:
|
||||
{
|
||||
if (last_line)
|
||||
{
|
||||
bounds.y -= bounds.height + last_line_bottom_offset;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
//- Draw lines
|
||||
|
||||
u64 line_number = 0;
|
||||
for (D_TextLine *line = first_line; line; line = line->next)
|
||||
{
|
||||
Vec2 draw_pos = bounds.pos;
|
||||
draw_pos.y += line_number * line_spacing - first_line_top_offset;
|
||||
|
||||
// Alignment
|
||||
switch (params.alignment)
|
||||
{
|
||||
case DRAW_TEXT_ALIGNMENT_LEFT: break;
|
||||
case DRAW_TEXT_ALIGNMENT_CENTER:
|
||||
{
|
||||
draw_pos.x += (bounds.width - line->line_width) / 2.f;
|
||||
} break;
|
||||
case DRAW_TEXT_ALIGNMENT_RIGHT:
|
||||
{
|
||||
draw_pos.x += bounds.width - line->line_width;
|
||||
} break;
|
||||
}
|
||||
|
||||
// Draw glyphs in line
|
||||
for (u64 i = 0; i < line->num_glyphs; ++i)
|
||||
{
|
||||
D_TextGlyph *tg = &line->glyphs[i];
|
||||
Vec2 pos = VEC2(draw_pos.x + tg->off_x, draw_pos.y + tg->off_y);
|
||||
Vec2 size = VEC2(tg->width, tg->height);
|
||||
Affine af = AffineFromRect(RectFromVec2(pos, size));
|
||||
D_DrawUiRect(sig, D_UIRECTPARAMS(.af = af, .texture = params.font->texture, .tint = params.color, .clip = tg->clip));
|
||||
draw_pos.x += tg->advance;
|
||||
|
||||
}
|
||||
++line_number;
|
||||
}
|
||||
|
||||
EndScratch(scratch);
|
||||
return bounds;
|
||||
}
|
||||
155
src/draw/draw.h
155
src/draw/draw.h
@ -1,155 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Material types
|
||||
|
||||
Struct(D_MaterialParams)
|
||||
{
|
||||
Affine af;
|
||||
GPU_Resource *texture;
|
||||
ClipRect clip;
|
||||
u32 tint;
|
||||
b32 is_light;
|
||||
Vec3 light_emittance;
|
||||
};
|
||||
#define D_MATERIALPARAMS(...) ((D_MaterialParams) { \
|
||||
.tint = Color_White, \
|
||||
.clip = AllClipped, \
|
||||
__VA_ARGS__ \
|
||||
})
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Ui types
|
||||
|
||||
Struct(D_UiRectParams)
|
||||
{
|
||||
Affine af;
|
||||
GPU_Resource *texture;
|
||||
ClipRect clip;
|
||||
u32 tint;
|
||||
};
|
||||
#define D_UIRECTPARAMS(...) ((D_UiRectParams) { \
|
||||
.tint = Color_White, \
|
||||
.clip = AllClipped, \
|
||||
__VA_ARGS__ \
|
||||
})
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Text types
|
||||
|
||||
// How is text aligned within its area
|
||||
Enum(D_TextAlignment)
|
||||
{
|
||||
DRAW_TEXT_ALIGNMENT_LEFT, // Default
|
||||
DRAW_TEXT_ALIGNMENT_CENTER,
|
||||
DRAW_TEXT_ALIGNMENT_RIGHT
|
||||
};
|
||||
|
||||
// How does the specified text position relate to the text area.
|
||||
// E.g. BOTTOM & RIGHT means the bottom-right of the text area will snap to
|
||||
// the specified position.
|
||||
Enum(D_TextOffsetX)
|
||||
{
|
||||
DRAW_TEXT_OFFSET_X_LEFT, // Default
|
||||
DRAW_TEXT_OFFSET_X_CENTER,
|
||||
DRAW_TEXT_OFFSET_X_RIGHT
|
||||
};
|
||||
|
||||
Enum(D_TextOffsetY)
|
||||
{
|
||||
DRAW_TEXT_OFFSET_Y_TOP, // Default
|
||||
DRAW_TEXT_OFFSET_Y_CENTER,
|
||||
DRAW_TEXT_OFFSET_Y_BOTTOM
|
||||
};
|
||||
|
||||
Struct(D_TextGlyph)
|
||||
{
|
||||
f32 off_x;
|
||||
f32 off_y;
|
||||
f32 width;
|
||||
f32 height;
|
||||
f32 advance;
|
||||
ClipRect clip;
|
||||
};
|
||||
|
||||
Struct(D_TextLine)
|
||||
{
|
||||
f32 line_width;
|
||||
u32 num_glyphs;
|
||||
D_TextGlyph *glyphs;
|
||||
D_TextLine *next;
|
||||
};
|
||||
|
||||
Struct(D_TextParams)
|
||||
{
|
||||
F_Font *font;
|
||||
Vec2 pos;
|
||||
f32 scale;
|
||||
u32 color;
|
||||
D_TextAlignment alignment;
|
||||
D_TextOffsetX offset_x;
|
||||
D_TextOffsetY offset_y;
|
||||
String str;
|
||||
};
|
||||
#define D_TEXTPARAMS(...) ((D_TextParams) { \
|
||||
.scale = 1.0, \
|
||||
.alignment = DRAW_TEXT_ALIGNMENT_LEFT, \
|
||||
.offset_x = DRAW_TEXT_OFFSET_X_LEFT, \
|
||||
.offset_y = DRAW_TEXT_OFFSET_Y_TOP, \
|
||||
.color = Color_White, \
|
||||
__VA_ARGS__ \
|
||||
})
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ State types
|
||||
|
||||
Struct(D_Ctx)
|
||||
{
|
||||
GPU_Resource *solid_white_texture;
|
||||
};
|
||||
|
||||
extern D_Ctx D;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Bootstrap
|
||||
|
||||
void D_Bootstrap(void);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Material
|
||||
|
||||
void D_DrawMaterial(GPU_RenderSig *sig, D_MaterialParams params);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Solid shape
|
||||
|
||||
void D_DrawPolyEx(GPU_RenderSig *sig, Vec2Array vertices, GPU_Indices indices, u32 color);
|
||||
void D_DrawPoly(GPU_RenderSig *sig, Vec2Array points, u32 color);
|
||||
void D_DrawCircle(GPU_RenderSig *sig, Vec2 pos, f32 radius, u32 color, u32 detail);
|
||||
void D_DrawQuad(GPU_RenderSig *sig, Quad quad, u32 color);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Line shape
|
||||
|
||||
void D_DrawLineGradient(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 start_color, u32 end_color);
|
||||
void D_DrawLine(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, u32 color);
|
||||
void D_DrawRay(GPU_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, u32 color);
|
||||
void D_DrawPolyLine(GPU_RenderSig *sig, Vec2Array points, b32 loop, f32 thickness, u32 color);
|
||||
void D_DrawCircleLine(GPU_RenderSig *sig, Vec2 pos, f32 radius, f32 thickness, u32 color, u32 detail);
|
||||
void D_DrawQuadLine(GPU_RenderSig *sig, Quad quad, f32 thickness, u32 color);
|
||||
void D_DrawArrowLine(GPU_RenderSig *sig, Vec2 start, Vec2 end, f32 thickness, f32 arrowhead_height, u32 color);
|
||||
void D_DrawArrowRay(GPU_RenderSig *sig, Vec2 pos, Vec2 rel, f32 thickness, f32 arrowhead_height, u32 color);
|
||||
void D_DrawColliderLine(GPU_RenderSig *sig, CLD_Shape shape, Affine shape_af, f32 thickness, u32 color, u32 detail);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Grid
|
||||
|
||||
void D_DrawGrid(GPU_RenderSig *sig, Affine af, u32 bg0_color, u32 bg1_color, u32 line_color, u32 x_color, u32 y_color, f32 thickness, f32 spacing, Vec2 offset);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Ui
|
||||
|
||||
void D_DrawUiRect(GPU_RenderSig *sig, D_UiRectParams params);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Text
|
||||
|
||||
Rect draw_text(GPU_RenderSig *sig, D_TextParams params);
|
||||
@ -1,22 +0,0 @@
|
||||
@Layer draw
|
||||
|
||||
//////////////////////////////
|
||||
//- Dependencies
|
||||
|
||||
@Dep base
|
||||
@Dep gpu
|
||||
@Dep sprite
|
||||
@Dep font
|
||||
@Dep collider
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC draw.h
|
||||
|
||||
@Bootstrap D_Bootstrap
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@IncludeC draw.c
|
||||
930
src/json/json.c
930
src/json/json.c
@ -1,930 +0,0 @@
|
||||
// TODO (if we want to be JSON standard compliant):
|
||||
// - Support unicode escape sequences in strings (\u)
|
||||
// - Don't allow leading 0s in numbers
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Lex
|
||||
|
||||
JSON_Token *JSON_PushToken(Arena *arena, JSON_TokenList *list)
|
||||
{
|
||||
JSON_Token *t = PushStruct(arena, JSON_Token);
|
||||
if (!list->token_first)
|
||||
{
|
||||
list->token_first = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->token_last->next = t;
|
||||
}
|
||||
list->token_last = t;
|
||||
return t;
|
||||
}
|
||||
|
||||
JSON_TokenList JSON_TokensFromString(Arena *arena, String src)
|
||||
{
|
||||
JSON_TokenList result = ZI;
|
||||
|
||||
JSON_Token *bof = JSON_PushToken(arena, &result);
|
||||
bof->kind = JSON_TokenKind_Bof;
|
||||
|
||||
u64 pos = 0;
|
||||
b32 lexing_done = 0;
|
||||
while (!lexing_done)
|
||||
{
|
||||
// Skip whitespace
|
||||
b32 whitespace_done = 0;
|
||||
while (!whitespace_done && pos < src.len)
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
whitespace_done = 1;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Newline:
|
||||
case JSON_Case_Space:
|
||||
{
|
||||
++pos;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create token
|
||||
JSON_Token *t = JSON_PushToken(arena, &result);
|
||||
t->start = pos;
|
||||
|
||||
if (pos >= src.len)
|
||||
{
|
||||
t->kind = JSON_TokenKind_Eof;
|
||||
t->next = t; // Self reference
|
||||
lexing_done = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Lex known token kinds
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default: break;
|
||||
|
||||
// Symbols
|
||||
case ',':
|
||||
{
|
||||
t->kind = JSON_TokenKind_Comma;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case ':':
|
||||
{
|
||||
t->kind = JSON_TokenKind_Colon;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '[':
|
||||
{
|
||||
t->kind = JSON_TokenKind_SquareBraceOpen;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case ']':
|
||||
{
|
||||
t->kind = JSON_TokenKind_SquareBraceClose;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '{':
|
||||
{
|
||||
t->kind = JSON_TokenKind_CurlyBraceOpen;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '}':
|
||||
{
|
||||
t->kind = JSON_TokenKind_CurlyBraceClose;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Number
|
||||
case '-':
|
||||
{
|
||||
// Verify '-' precedes digit
|
||||
b32 next_is_digit = 0;
|
||||
if ((pos + 1) < src.len)
|
||||
{
|
||||
switch (src.text[pos + 1])
|
||||
{
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
next_is_digit = 1;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
++pos;
|
||||
if (!next_is_digit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
} FALLTHROUGH;
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
t->kind = JSON_TokenKind_Number;
|
||||
JSON_LexNumberState state = JSON_LexNumberState_Whole;
|
||||
b32 number_done = 0;
|
||||
while (!number_done && pos < src.len)
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
number_done = 1;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '.':
|
||||
{
|
||||
u64 consume = 0;
|
||||
if (state == JSON_LexNumberState_Whole && (pos + 1) < src.len)
|
||||
{
|
||||
u8 c1 = src.text[pos + 1];
|
||||
switch (c1)
|
||||
{
|
||||
default: break;
|
||||
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
// Consume '.'
|
||||
++consume;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (consume)
|
||||
{
|
||||
state = JSON_LexNumberState_Fraction;
|
||||
pos += consume;
|
||||
}
|
||||
else
|
||||
{
|
||||
number_done = 1;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
{
|
||||
u64 consume = 0;
|
||||
if ((state == JSON_LexNumberState_Whole || state == JSON_LexNumberState_Fraction) && (pos + 1) < src.len)
|
||||
{
|
||||
u8 c1 = src.text[pos + 1];
|
||||
switch (c1)
|
||||
{
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
// Consume 'E'/'e'
|
||||
++consume;
|
||||
} break;
|
||||
|
||||
case '-':
|
||||
case '+':
|
||||
{
|
||||
if ((pos + 2) < src.len)
|
||||
{
|
||||
u8 c2 = src.text[pos + 2];
|
||||
switch (c2)
|
||||
{
|
||||
default: break;
|
||||
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
// Consume 'E'/'e' & '+'/'-'
|
||||
consume += 2;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (consume)
|
||||
{
|
||||
state = JSON_LexNumberState_Exponent;
|
||||
pos += consume;
|
||||
}
|
||||
else
|
||||
{
|
||||
number_done = 1;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
// String
|
||||
case '"':
|
||||
{
|
||||
++pos;
|
||||
|
||||
b32 string_done = 0;
|
||||
b32 next_escaped = 0;
|
||||
while (!string_done && pos < src.len)
|
||||
{
|
||||
b32 escaped = next_escaped;
|
||||
next_escaped = 0;
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Newline:
|
||||
{
|
||||
++pos;
|
||||
string_done = 1;
|
||||
} break;
|
||||
|
||||
case '"':
|
||||
{
|
||||
++pos;
|
||||
if (!escaped)
|
||||
{
|
||||
t->kind = JSON_TokenKind_String;
|
||||
string_done = 1;
|
||||
}
|
||||
} break;
|
||||
|
||||
case '\\':
|
||||
{
|
||||
++pos;
|
||||
if (!escaped)
|
||||
{
|
||||
next_escaped = 1;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
// Keywords
|
||||
case 't':
|
||||
case 'f':
|
||||
case 'n':
|
||||
{
|
||||
String keyword = JSON_keyword_strings[src.text[pos]];
|
||||
|
||||
b32 match = 1;
|
||||
if ((pos + keyword.len - 1) < src.len)
|
||||
{
|
||||
if ((pos + keyword.len) < src.len)
|
||||
{
|
||||
// Don't match if word continues past keyword
|
||||
switch (src.text[pos + keyword.len])
|
||||
{
|
||||
default:
|
||||
{
|
||||
match = 0;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Symbol:
|
||||
case JSON_Case_Space:
|
||||
case JSON_Case_Newline:
|
||||
{
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (match)
|
||||
{
|
||||
String cmp_str = {
|
||||
.len = keyword.len,
|
||||
.text = &src.text[pos]
|
||||
};
|
||||
match = MatchString(cmp_str, keyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
t->kind = JSON_keyword_types[src.text[pos]];
|
||||
pos += keyword.len;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Lex unknown token
|
||||
if (t->kind == JSON_TokenKind_Unknown)
|
||||
{
|
||||
b32 unknown_done = 0;
|
||||
while (!unknown_done && pos < src.len)
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Symbol:
|
||||
case JSON_Case_Space:
|
||||
case JSON_Case_Newline:
|
||||
{
|
||||
unknown_done = 1;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
t->end = pos;
|
||||
|
||||
// Exit early if unknown token encountered
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
t->end = pos;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Interpret
|
||||
|
||||
f64 interpret_number(String src)
|
||||
{
|
||||
b32 whole_present = 0;
|
||||
u64 whole_left = 0;
|
||||
u64 whole_right = 0;
|
||||
i32 whole_sign = 1;
|
||||
|
||||
b32 fraction_present = 0;
|
||||
u64 fraction_left = 0;
|
||||
u64 fraction_right = 0;
|
||||
|
||||
b32 exponent_present = 0;
|
||||
u64 exponent_left = 0;
|
||||
u64 exponent_right = 0;
|
||||
i32 exponent_sign = 1;
|
||||
|
||||
// Lex number parts
|
||||
{
|
||||
u64 pos = 0;
|
||||
if (src.len > 0 && src.text[0] == '-')
|
||||
{
|
||||
whole_sign = -1;
|
||||
++pos;
|
||||
}
|
||||
|
||||
JSON_LexNumberState state = JSON_LexNumberState_Whole;
|
||||
while (pos < src.len)
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
// Unreachable
|
||||
Assert(0);
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_Case_Digit0Through9:
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case JSON_LexNumberState_Whole:
|
||||
{
|
||||
if (!whole_present)
|
||||
{
|
||||
whole_present = 1;
|
||||
whole_left = pos;
|
||||
}
|
||||
whole_right = pos;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_LexNumberState_Fraction:
|
||||
{
|
||||
if (!fraction_present)
|
||||
{
|
||||
fraction_present = 1;
|
||||
fraction_left = pos;
|
||||
}
|
||||
fraction_right = pos;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_LexNumberState_Exponent:
|
||||
{
|
||||
if (!exponent_present)
|
||||
{
|
||||
exponent_present = 1;
|
||||
exponent_left = pos;
|
||||
}
|
||||
exponent_right = pos;
|
||||
++pos;
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case '.':
|
||||
{
|
||||
state = JSON_LexNumberState_Fraction;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
{
|
||||
state = JSON_LexNumberState_Exponent;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '-':
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
default:
|
||||
{
|
||||
// Unreachable
|
||||
Assert(0);
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_LexNumberState_Whole:
|
||||
{
|
||||
whole_sign = -1;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_LexNumberState_Exponent:
|
||||
{
|
||||
exponent_sign = -1;
|
||||
++pos;
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case '+':
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
default:
|
||||
{
|
||||
// Unreachable
|
||||
Assert(0);
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case JSON_LexNumberState_Exponent:
|
||||
{
|
||||
exponent_sign = 1;
|
||||
++pos;
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f64 result = 0;
|
||||
|
||||
// Process whole part
|
||||
if (whole_present)
|
||||
{
|
||||
u64 pos = whole_left;
|
||||
while (pos <= whole_right)
|
||||
{
|
||||
u8 digit = MinU8(src.text[pos] - 48, 9);
|
||||
u64 exp = whole_right - pos;
|
||||
result += digit * PowU64(10, exp);
|
||||
++pos;
|
||||
}
|
||||
result *= whole_sign;
|
||||
}
|
||||
|
||||
// Process fraction part
|
||||
if (fraction_present)
|
||||
{
|
||||
u64 frac_whole = 0;
|
||||
u64 pos = fraction_left;
|
||||
while (pos <= fraction_right)
|
||||
{
|
||||
u8 digit = MinU8(src.text[pos] - 48, 9);
|
||||
u64 exp = fraction_right - pos;
|
||||
frac_whole += digit * PowU64(10, exp);
|
||||
++pos;
|
||||
}
|
||||
|
||||
result += (f64)frac_whole / PowU64(10, (fraction_right - fraction_left + 1));
|
||||
}
|
||||
|
||||
// Process exponent part
|
||||
if (exponent_present)
|
||||
{
|
||||
u64 exponent_whole = 0;
|
||||
u64 pos = exponent_left;
|
||||
while (pos <= exponent_right)
|
||||
{
|
||||
u8 digit = MinU8(src.text[pos] - 48, 9);
|
||||
u64 exp = exponent_right - pos;
|
||||
exponent_whole += digit * PowU64(10, exp);
|
||||
++pos;
|
||||
}
|
||||
|
||||
if (exponent_sign >= 0)
|
||||
{
|
||||
result *= PowU64(10, exponent_whole);
|
||||
}
|
||||
else
|
||||
{
|
||||
result /= PowU64(10, exponent_whole);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String interpret_string(Arena *arena, String src, String *error)
|
||||
{
|
||||
String result = {
|
||||
.len = 0,
|
||||
.text = PushDry(arena, u8)
|
||||
};
|
||||
|
||||
if (src.len < 2)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = Lit("Malformed string.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Ignore beginning quote
|
||||
u64 pos = 1;
|
||||
|
||||
b32 valid_close = 0;
|
||||
b32 string_done = 0;
|
||||
b32 next_escaped = 0;
|
||||
while (!string_done && pos < src.len)
|
||||
{
|
||||
b32 escaped = next_escaped;
|
||||
next_escaped = 0;
|
||||
|
||||
if (escaped)
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = Lit("Invalid escape character in string.");
|
||||
return result;
|
||||
}
|
||||
} break;
|
||||
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = src.text[pos];
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Backspace
|
||||
case 'b':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = '\b';
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Formfeed
|
||||
case 'f':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = '\f';
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Linefeed
|
||||
case 'n':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = '\n';
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Carriage return
|
||||
case 'r':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = '\r';
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// Horizontal tab
|
||||
case 't':
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = '\t';
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
// TODO: Unicode escape support
|
||||
// case 'u':
|
||||
// {
|
||||
// // TODO
|
||||
// } break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (src.text[pos])
|
||||
{
|
||||
default:
|
||||
{
|
||||
*PushStructNoZero(arena, u8) = src.text[pos];
|
||||
++result.len;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '\\':
|
||||
{
|
||||
escaped = 1;
|
||||
++pos;
|
||||
} break;
|
||||
|
||||
case '"':
|
||||
{
|
||||
string_done = 1;
|
||||
valid_close = 1;
|
||||
++pos;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_close)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = Lit("Expected end of string.");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Parse
|
||||
|
||||
void JSON_PushError(Arena *arena, JSON_Parser *p, JSON_Token *t, String msg)
|
||||
{
|
||||
JSON_Error *error = PushStruct(arena, JSON_Error);
|
||||
error->msg = msg;
|
||||
error->start = t->start;
|
||||
error->end = t->end;
|
||||
|
||||
JSON_ErrorList *list = &p->errors;
|
||||
if (!list->first)
|
||||
{
|
||||
list->first = error;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->last->next = error;
|
||||
}
|
||||
list->last = error;
|
||||
++list->count;
|
||||
}
|
||||
|
||||
void JSON_Parse(Arena *arena, JSON_Parser *p)
|
||||
{
|
||||
TempArena scratch = BeginScratch(arena);
|
||||
|
||||
JSON_Blob *root = PushStruct(arena, JSON_Blob);
|
||||
JSON_Token *at = p->at;
|
||||
String src = p->src;
|
||||
|
||||
if (at->kind == JSON_TokenKind_Bof)
|
||||
{
|
||||
at = at->next;
|
||||
}
|
||||
|
||||
// Depth first stack
|
||||
*PushStructNoZero(scratch.arena, JSON_Blob *) = root;
|
||||
u64 stack_count = 1;
|
||||
|
||||
while (stack_count > 0)
|
||||
{
|
||||
JSON_Blob *json = 0;
|
||||
PopStruct(scratch.arena, JSON_Blob *, &json);
|
||||
--stack_count;
|
||||
|
||||
JSON_Blob *parent_json = json->parent;
|
||||
b32 is_new_parent = 0;
|
||||
if (json->type == JSON_Type_Object || json->type == JSON_Type_Array)
|
||||
{
|
||||
// No more children to parse for object/array, check for closing brace.
|
||||
JSON_TokenKind tok_close_kind = json->type == JSON_Type_Object ? JSON_TokenKind_CurlyBraceClose : JSON_TokenKind_SquareBraceClose;
|
||||
if (at->kind == tok_close_kind)
|
||||
{
|
||||
at = at->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_PushError(arena, p, at, Lit("Expected comma."));
|
||||
at = at->next;
|
||||
goto abort;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent_json)
|
||||
{
|
||||
if (parent_json->type == JSON_Type_Object)
|
||||
{
|
||||
// Parse key
|
||||
if (at->kind == JSON_TokenKind_String)
|
||||
{
|
||||
String t_text = (String) { .len = at->end - at->start, .text = &src.text[at->start] };
|
||||
String error = ZI;
|
||||
String key = interpret_string(arena, t_text, &error);
|
||||
if (error.len > 0)
|
||||
{
|
||||
JSON_PushError(arena, p, at, error);
|
||||
goto abort;
|
||||
}
|
||||
else
|
||||
{
|
||||
json->key = key;
|
||||
at = at->next;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_PushError(arena, p, at, Lit("Key expected."));
|
||||
goto abort;
|
||||
}
|
||||
|
||||
// Parse colon
|
||||
if (at->kind == JSON_TokenKind_Colon)
|
||||
{
|
||||
at = at->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_PushError(arena, p, at, Lit("Colon expected."));
|
||||
goto abort;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent_json->child_last)
|
||||
{
|
||||
parent_json->child_last->next = json;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent_json->child_first = json;
|
||||
}
|
||||
parent_json->child_last = json;
|
||||
}
|
||||
|
||||
// Parse value
|
||||
switch (at->kind)
|
||||
{
|
||||
default:
|
||||
{
|
||||
JSON_PushError(arena, p, at, Lit("Value expected."));
|
||||
at = at->next;
|
||||
goto abort;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_Number:
|
||||
{
|
||||
String t_text = STRING(at->end - at->start, &src.text[at->start]);
|
||||
f64 value = interpret_number(t_text);
|
||||
json->type = JSON_Type_Number;
|
||||
json->value.number = value;
|
||||
at = at->next;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_String:
|
||||
{
|
||||
String t_text = STRING(at->end - at->start, &src.text[at->start]);
|
||||
String error = ZI;
|
||||
String value = interpret_string(arena, t_text, &error);
|
||||
if (error.len > 0)
|
||||
{
|
||||
JSON_PushError(arena, p, at, error);
|
||||
goto abort;
|
||||
}
|
||||
else
|
||||
{
|
||||
json->type = JSON_Type_String;
|
||||
json->value.string = value;
|
||||
at = at->next;
|
||||
}
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_KeywordTrue:
|
||||
{
|
||||
json->type = JSON_Type_Bool;
|
||||
json->value.boolean = 1;
|
||||
at = at->next;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_KeywordFalse:
|
||||
{
|
||||
json->type = JSON_Type_Bool;
|
||||
json->value.boolean = 0;
|
||||
at = at->next;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_KeywordNull:
|
||||
{
|
||||
json->type = JSON_Type_Null;
|
||||
at = at->next;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_CurlyBraceOpen:
|
||||
{
|
||||
json->type = JSON_Type_Object;
|
||||
at = at->next;
|
||||
is_new_parent = 1;
|
||||
} break;
|
||||
|
||||
case JSON_TokenKind_SquareBraceOpen:
|
||||
{
|
||||
json->type = JSON_Type_Array;
|
||||
at = at->next;
|
||||
is_new_parent = 1;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new_parent)
|
||||
{
|
||||
// Push self back to stack to re-check for closing brace later
|
||||
*PushStructNoZero(scratch.arena, JSON_Blob *) = json;
|
||||
++stack_count;
|
||||
|
||||
// Create child & push to stack
|
||||
JSON_Blob *child = PushStruct(arena, JSON_Blob);
|
||||
child->parent = json;
|
||||
*PushStructNoZero(scratch.arena, JSON_Blob *) = child;
|
||||
++stack_count;
|
||||
}
|
||||
else if (parent_json)
|
||||
{
|
||||
// Check for comma
|
||||
if (at->kind == JSON_TokenKind_Comma)
|
||||
{
|
||||
// Create sibling & push to stack
|
||||
JSON_Blob *sibling = PushStruct(arena, JSON_Blob);
|
||||
sibling->parent = parent_json;
|
||||
*PushStructNoZero(scratch.arena, JSON_Blob *) = sibling;
|
||||
++stack_count;
|
||||
at = at->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abort:
|
||||
|
||||
p->at = at;
|
||||
p->root = root;
|
||||
|
||||
EndScratch(scratch);
|
||||
}
|
||||
|
||||
JSON_Result JSON_BlobFromString(Arena *arena, String src)
|
||||
{
|
||||
TempArena scratch = BeginScratch(arena);
|
||||
|
||||
JSON_TokenList tl = JSON_TokensFromString(scratch.arena, src);
|
||||
|
||||
// Parse root
|
||||
JSON_Parser p = ZI;
|
||||
p.src = src;
|
||||
p.at = tl.token_first;
|
||||
JSON_Parse(arena, &p);
|
||||
|
||||
// Verify end of file
|
||||
if (p.errors.count == 0 && p.at->kind != JSON_TokenKind_Eof)
|
||||
{
|
||||
JSON_PushError(arena, &p, p.at, Lit("Expected end of file."));
|
||||
}
|
||||
|
||||
EndScratch(scratch);
|
||||
|
||||
JSON_Result result = ZI;
|
||||
result.root = p.root;
|
||||
result.errors = p.errors;
|
||||
return result;
|
||||
}
|
||||
159
src/json/json.h
159
src/json/json.h
@ -1,159 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Blob types
|
||||
|
||||
Enum(JSON_Type)
|
||||
{
|
||||
JSON_Type_Null,
|
||||
JSON_Type_Bool,
|
||||
JSON_Type_Number,
|
||||
JSON_Type_String,
|
||||
JSON_Type_Array,
|
||||
JSON_Type_Object
|
||||
};
|
||||
|
||||
Struct(JSON_Blob)
|
||||
{
|
||||
JSON_Type type;
|
||||
String key;
|
||||
|
||||
JSON_Blob *parent;
|
||||
JSON_Blob *next;
|
||||
JSON_Blob *child_first;
|
||||
JSON_Blob *child_last;
|
||||
|
||||
union
|
||||
{
|
||||
String string;
|
||||
f64 number;
|
||||
b32 boolean;
|
||||
} value;
|
||||
};
|
||||
|
||||
Struct(JSON_Error)
|
||||
{
|
||||
String msg;
|
||||
u64 start;
|
||||
u64 end;
|
||||
JSON_Error *next;
|
||||
};
|
||||
|
||||
Struct(JSON_ErrorList)
|
||||
{
|
||||
u64 count;
|
||||
JSON_Error *first;
|
||||
JSON_Error *last;
|
||||
};
|
||||
|
||||
Struct(JSON_Result)
|
||||
{
|
||||
JSON_Blob *root;
|
||||
JSON_ErrorList errors;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Lexer types
|
||||
|
||||
#define JSON_Case_Newline \
|
||||
0x0A: /* Line feed or New line */ \
|
||||
case 0x0D /* Carriage return */
|
||||
|
||||
#define JSON_Case_Space \
|
||||
0x20: /* Space */ \
|
||||
case 0x09 /* Horizontal tab */
|
||||
|
||||
#define JSON_Case_Digit0Through9 \
|
||||
'0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9'
|
||||
|
||||
#define JSON_Case_Digit1Through9 \
|
||||
'1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9'
|
||||
|
||||
#define JSON_Case_Symbol \
|
||||
',': case ':': case '[': case ']': case '{': case '}'
|
||||
|
||||
Enum(JSON_TokenKind)
|
||||
{
|
||||
JSON_TokenKind_Unknown,
|
||||
|
||||
JSON_TokenKind_Number,
|
||||
JSON_TokenKind_String,
|
||||
|
||||
JSON_TokenKind_KeywordTrue,
|
||||
JSON_TokenKind_KeywordFalse,
|
||||
JSON_TokenKind_KeywordNull,
|
||||
|
||||
JSON_TokenKind_Comma,
|
||||
JSON_TokenKind_Colon,
|
||||
JSON_TokenKind_SquareBraceOpen,
|
||||
JSON_TokenKind_SquareBraceClose,
|
||||
JSON_TokenKind_CurlyBraceOpen,
|
||||
JSON_TokenKind_CurlyBraceClose,
|
||||
|
||||
JSON_TokenKind_Bof,
|
||||
JSON_TokenKind_Eof
|
||||
};
|
||||
|
||||
Struct(JSON_Token)
|
||||
{
|
||||
JSON_TokenKind kind;
|
||||
u64 start;
|
||||
u64 end;
|
||||
JSON_Token *next;
|
||||
};
|
||||
|
||||
Struct(JSON_TokenList)
|
||||
{
|
||||
JSON_Token *token_first;
|
||||
JSON_Token *token_last;
|
||||
};
|
||||
|
||||
Enum(JSON_LexNumberState)
|
||||
{
|
||||
JSON_LexNumberState_Whole,
|
||||
JSON_LexNumberState_Fraction,
|
||||
JSON_LexNumberState_Exponent
|
||||
};
|
||||
|
||||
Global Readonly String JSON_keyword_strings[] = {
|
||||
['t'] = CompLit("true"),
|
||||
['f'] = CompLit("false"),
|
||||
['n'] = CompLit("null")
|
||||
};
|
||||
|
||||
Global Readonly JSON_TokenKind JSON_keyword_types[] = {
|
||||
['t'] = JSON_TokenKind_KeywordTrue,
|
||||
['f'] = JSON_TokenKind_KeywordFalse,
|
||||
['n'] = JSON_TokenKind_KeywordNull
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Parser types
|
||||
|
||||
Struct(JSON_Parser)
|
||||
{
|
||||
// Input
|
||||
String src;
|
||||
JSON_Token *at;
|
||||
|
||||
// Output
|
||||
JSON_Blob *root;
|
||||
JSON_ErrorList errors;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Lex
|
||||
|
||||
JSON_Token *JSON_PushToken(Arena *arena, JSON_TokenList *list);
|
||||
JSON_TokenList JSON_TokensFromString(Arena *arena, String src);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Interpret
|
||||
|
||||
f64 interpret_number(String src);
|
||||
String interpret_string(Arena *arena, String src, String *error);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Parse
|
||||
|
||||
void JSON_PushError(Arena *arena, JSON_Parser *p, JSON_Token *t, String msg);
|
||||
void JSON_Parse(Arena *arena, JSON_Parser *p);
|
||||
JSON_Result JSON_BlobFromString(Arena *arena, String src);
|
||||
@ -1,16 +0,0 @@
|
||||
@Layer json
|
||||
|
||||
//////////////////////////////
|
||||
//- Dependencies
|
||||
|
||||
@Dep base
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC json_core.h
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@IncludeC json_core.c
|
||||
@ -1,20 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Mp3 types
|
||||
|
||||
Enum(MP3_DecodeFlag)
|
||||
{
|
||||
MP3_DecodeFlag_None = 0,
|
||||
MP3_DecodeFlag_Stereo = (1 << 0),
|
||||
};
|
||||
|
||||
Struct(MP3_Result)
|
||||
{
|
||||
u64 samples_count;
|
||||
i16 *samples;
|
||||
b32 ok;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Mp3
|
||||
|
||||
MP3_Result MP3_Decode(Arena *arena, String encoded, u32 sample_rate, MP3_DecodeFlag flags);
|
||||
@ -1,11 +0,0 @@
|
||||
@Layer mp3
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC mp3.h
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@DefaultDownstream mp3_mmf
|
||||
@ -1,120 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Decode
|
||||
|
||||
MP3_Result MP3_Decode(Arena *arena, String encoded, u32 sample_rate, MP3_DecodeFlag flags)
|
||||
{
|
||||
MP3_Result result = ZI;
|
||||
u64 bytes_per_sample = 2;
|
||||
|
||||
u64 channel_count;
|
||||
u32 channel_mask;
|
||||
if (flags & MP3_DecodeFlag_Stereo)
|
||||
{
|
||||
channel_count = 2;
|
||||
channel_mask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
channel_count = 1;
|
||||
channel_mask = SPEAKER_FRONT_CENTER;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
//- Startup
|
||||
|
||||
MFStartup(MF_VERSION, MFSTARTUP_LITE);
|
||||
|
||||
// Create IStream from encoded string
|
||||
IStream *i_stream = SHCreateMemStream(encoded.text, encoded.len);
|
||||
|
||||
// Create IMFByteStream from IStream
|
||||
IMFByteStream *byte_stream = 0;
|
||||
MFCreateMFByteStreamOnStream(i_stream, &byte_stream);
|
||||
|
||||
// Create reader from IMFByteStream
|
||||
IMFSourceReader *reader;
|
||||
MFCreateSourceReaderFromByteStream(byte_stream, 0, &reader);
|
||||
|
||||
//////////////////////////////
|
||||
//- Get media type
|
||||
|
||||
// Read only first audio stream
|
||||
IMFSourceReader_SetStreamSelection(reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS, 0);
|
||||
IMFSourceReader_SetStreamSelection(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 1);
|
||||
|
||||
WAVEFORMATEXTENSIBLE format = {
|
||||
.Format = {
|
||||
.wFormatTag = WAVE_FORMAT_EXTENSIBLE,
|
||||
.nChannels = (WORD)channel_count,
|
||||
.nSamplesPerSec = (WORD)sample_rate,
|
||||
.nAvgBytesPerSec = (DWORD)(sample_rate * channel_count * bytes_per_sample),
|
||||
.nBlockAlign = (WORD)(channel_count * bytes_per_sample),
|
||||
.wBitsPerSample = (WORD)(8 * bytes_per_sample),
|
||||
.cbSize = sizeof(format) - sizeof(format.Format)
|
||||
},
|
||||
.Samples.wValidBitsPerSample = 8 * bytes_per_sample,
|
||||
.dwChannelMask = channel_mask,
|
||||
.SubFormat = MEDIASUBTYPE_PCM
|
||||
};
|
||||
|
||||
// Media Foundation in Windows 8+ allows reader to convert output to different format than native
|
||||
IMFMediaType *type;
|
||||
MFCreateMediaType(&type);
|
||||
MFInitMediaTypeFromWaveFormatEx(type, &format.Format, sizeof(format));
|
||||
IMFSourceReader_SetCurrentMediaType(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, type);
|
||||
IMFMediaType_Release(type);
|
||||
|
||||
//////////////////////////////
|
||||
//- Read
|
||||
|
||||
result.samples = ArenaNext(arena, i16);
|
||||
u64 sample_bytes_read = 0;
|
||||
for (;;)
|
||||
{
|
||||
IMFSample *sample;
|
||||
DWORD sample_flags = 0;
|
||||
HRESULT hr = IMFSourceReader_ReadSample(reader, (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, 0, &sample_flags, 0, &sample);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if done
|
||||
if (sample_flags & MF_SOURCE_READERF_ENDOFSTREAM)
|
||||
{
|
||||
result.ok = 1;
|
||||
break;
|
||||
}
|
||||
Assert(sample_flags == 0);
|
||||
|
||||
// Read samples
|
||||
IMFMediaBuffer *buffer;
|
||||
IMFSample_ConvertToContiguousBuffer(sample, &buffer);
|
||||
|
||||
BYTE *src;
|
||||
DWORD size_bytes;
|
||||
IMFMediaBuffer_Lock(buffer, &src, 0, &size_bytes);
|
||||
{
|
||||
i16 *dst = PushStructsNoZero(arena, i16, (size_bytes + 1) >> 1);
|
||||
CopyBytes(dst, src, size_bytes);
|
||||
sample_bytes_read += size_bytes;
|
||||
}
|
||||
IMFMediaBuffer_Unlock(buffer);
|
||||
|
||||
IMediaBuffer_Release(buffer);
|
||||
IMFSample_Release(sample);
|
||||
}
|
||||
|
||||
result.samples_count = sample_bytes_read / bytes_per_sample;
|
||||
|
||||
//////////////////////////////
|
||||
//- Cleanup
|
||||
|
||||
IMFSourceReader_Release(reader);
|
||||
IMFByteStream_Close(byte_stream);
|
||||
// FIXME: Enable this
|
||||
//IStream_Release(i_stream);
|
||||
MFShutdown();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Windows headers
|
||||
|
||||
#pragma warning(push, 0)
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <objidl.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
#pragma comment(lib, "mfplat")
|
||||
#pragma comment(lib, "mfreadwrite")
|
||||
#pragma comment(lib, "shlwapi")
|
||||
@ -1,11 +0,0 @@
|
||||
@Layer mp3_mmf
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC mp3_mmf.h
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@IncludeC mp3_mmf.c
|
||||
@ -1,133 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Load job
|
||||
|
||||
JobImpl(SND_Load, sig, id)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
ResourceKey resource = sig->resource;
|
||||
String name = NameFromResource(resource);
|
||||
AC_Asset *asset = sig->asset;
|
||||
SND_SoundFlag flags = sig->flags;
|
||||
|
||||
LogInfoF("Loading sound \"%F\"", FmtString(name));
|
||||
i64 start_ns = TimeNs();
|
||||
|
||||
String error_msg = Lit("Unknown error");
|
||||
|
||||
// Decode
|
||||
MP3_Result decoded = ZI;
|
||||
String resource_data = DataFromResource(resource);
|
||||
if (resource_data.len > 0)
|
||||
{
|
||||
u64 decode_flags = 0;
|
||||
if (flags & SND_SoundFlag_Stereo)
|
||||
{
|
||||
decode_flags |= MP3_DecodeFlag_Stereo;
|
||||
}
|
||||
decoded = MP3_Decode(scratch.arena, resource_data, SND_SampleRate, decode_flags);
|
||||
if (!decoded.ok)
|
||||
{
|
||||
error_msg = Lit("Failed to decode sound file");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error_msg = Lit("Missing resource data");
|
||||
}
|
||||
|
||||
if (decoded.ok)
|
||||
{
|
||||
// Store
|
||||
SND_Sound *sound = 0;
|
||||
u64 samples_count = decoded.samples_count;
|
||||
i16 *samples = 0;
|
||||
{
|
||||
AC_Store store = AC_OpenStore();
|
||||
sound = PushStructNoZero(store.arena, SND_Sound);
|
||||
samples = PushStructsNoZero(store.arena, i16, samples_count);
|
||||
AC_CloseStore(&store);
|
||||
}
|
||||
|
||||
// Initialize
|
||||
ZeroStruct(sound);
|
||||
sound->flags = flags;
|
||||
sound->samples_count = samples_count;
|
||||
sound->samples = samples;
|
||||
CopyBytes(sound->samples, decoded.samples, decoded.samples_count * sizeof(*decoded.samples));
|
||||
|
||||
LogSuccessF("Loaded sound \"%F\" in %F seconds", FmtString(name), FmtFloat(SecondsFromNs(TimeNs() - start_ns)));
|
||||
AC_MarkReady(asset, sound);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogErrorF("Error loading sound \"%F\": %F", FmtString(name), FmtString(error_msg));
|
||||
|
||||
// Store
|
||||
SND_Sound *sound = 0;
|
||||
{
|
||||
AC_Store store = AC_OpenStore();
|
||||
sound = PushStructNoZero(store.arena, SND_Sound);
|
||||
AC_CloseStore(&store);
|
||||
}
|
||||
*sound = (SND_Sound) { 0 };
|
||||
AC_MarkReady(asset, sound);
|
||||
}
|
||||
|
||||
EndScratch(scratch);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Load sound
|
||||
|
||||
AC_Asset *SND_LoadAsset(ResourceKey resource, SND_SoundFlag flags, b32 wait)
|
||||
{
|
||||
TempArena scratch = BeginScratchNoConflict();
|
||||
|
||||
// Generate and append sound flags to name key
|
||||
String name = NameFromResource(resource);
|
||||
String key = StringF(
|
||||
scratch.arena,
|
||||
"%F%F_sound",
|
||||
FmtString(name),
|
||||
FmtUint((u64)flags)
|
||||
);
|
||||
u64 hash = AC_HashFromKey(key);
|
||||
b32 is_first_touch;
|
||||
AC_Asset *asset = AC_TouchCache(key, hash, &is_first_touch);
|
||||
|
||||
if (is_first_touch)
|
||||
{
|
||||
AC_MarkLoading(asset);
|
||||
{
|
||||
Job *job = OpenJob(SND_Load, AsyncPool());
|
||||
SND_Load_Sig *sig = PushStruct(job->arena, SND_Load_Sig);
|
||||
job->sig = sig;
|
||||
sig->resource = resource;
|
||||
sig->asset = asset;
|
||||
sig->flags = flags;
|
||||
CloseJob(job);
|
||||
}
|
||||
if (wait)
|
||||
{
|
||||
AC_YieldOnAssetReady(asset);
|
||||
}
|
||||
}
|
||||
|
||||
EndScratch(scratch);
|
||||
return asset;
|
||||
}
|
||||
|
||||
SND_Sound *SND_LoadSoundAsync(ResourceKey resource, SND_SoundFlag flags)
|
||||
{
|
||||
AC_Asset *asset = SND_LoadAsset(resource, flags, 0);
|
||||
SND_Sound *sound = (SND_Sound *)AC_DataFromStore(asset);
|
||||
return sound;
|
||||
}
|
||||
|
||||
SND_Sound *SND_LoadSoundWait(ResourceKey resource, SND_SoundFlag flags)
|
||||
{
|
||||
AC_Asset *asset = SND_LoadAsset(resource, flags, 1);
|
||||
AC_YieldOnAssetReady(asset);
|
||||
SND_Sound *sound = (SND_Sound *)AC_DataFromStore(asset);
|
||||
return sound;
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Sound types
|
||||
|
||||
#define SND_SampleRate 48000
|
||||
|
||||
Enum(SND_SoundFlag)
|
||||
{
|
||||
SND_SoundFlag_None = 0,
|
||||
SND_SoundFlag_Stereo = (1 << 0)
|
||||
};
|
||||
|
||||
Struct(SND_Sound)
|
||||
{
|
||||
SND_SoundFlag flags;
|
||||
u64 samples_count;
|
||||
i16 *samples;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Load sound
|
||||
|
||||
JobDecl(SND_Load, { SND_SoundFlag flags; AC_Asset *asset; ResourceKey resource; });
|
||||
AC_Asset *SND_LoadAsset(ResourceKey resource, SND_SoundFlag flags, b32 wait);
|
||||
SND_Sound *SND_LoadSoundAsync(ResourceKey resource, SND_SoundFlag flags);
|
||||
SND_Sound *SND_LoadSoundWait(ResourceKey resource, SND_SoundFlag flags);
|
||||
@ -1,18 +0,0 @@
|
||||
@Layer sound
|
||||
|
||||
//////////////////////////////
|
||||
//- Dependencies
|
||||
|
||||
@Dep platform
|
||||
@Dep mp3
|
||||
@Dep asset_cache
|
||||
|
||||
//////////////////////////////
|
||||
//- Api
|
||||
|
||||
@IncludeC sound.h
|
||||
|
||||
//////////////////////////////
|
||||
//- Impl
|
||||
|
||||
@IncludeC sound.c
|
||||
Loading…
Reference in New Issue
Block a user