Skip to content

Commit 0a8ec6c

Browse files
authored
Refactor docs (#56)
2 parents 354ad34 + 315ddf4 commit 0a8ec6c

8 files changed

Lines changed: 170 additions & 303 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@
1010
[submodule "extern/fast-capi"]
1111
path = extern/fast-capi
1212
url = https://github.com/Evilpasture/fast-capi.git
13+
[submodule "extern/docs-embedder"]
14+
path = extern/docs-embedder
15+
url = https://github.com/Evilpasture/docs-embedder.git

CMakeLists.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,19 @@ if(ipo_supported AND NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND WIN32))
522522
set_property(TARGET ${FAST_CAPI_TARGET} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
523523
endif()
524524

525+
# ==============================================================================
526+
# 4.2 BUILD DOCS-EMBEDDER (Via Submodule CMake)
527+
# ==============================================================================
528+
add_subdirectory("extern/docs-embedder")
529+
530+
# Apply Culverin's high-performance flags to the submodule
531+
target_compile_options(docs_embedder PRIVATE ${CULVERIN_COMPILE_FLAGS})
532+
533+
# Enforce IPO/LTO if supported
534+
if(ipo_supported AND NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND WIN32))
535+
set_property(TARGET docs_embedder PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
536+
endif()
537+
525538
# --- #embed Fallback Generator for Ancient Compilers which are probably older than me ---
526539
# 1. Ensure the generated directory exists immediately because sometimes it wouldn't work on other environments or compilers
527540
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
@@ -676,7 +689,7 @@ if(WIN32)
676689
target_link_libraries(_culverin_c PRIVATE Winmm.lib)
677690
endif()
678691
target_compile_definitions(_culverin_c PRIVATE JPH_DEBUG_RENDERER)
679-
target_link_libraries(_culverin_c PRIVATE ${MAG_MUTEX_TARGET} ${JOLTC_TARGET} fast_capi_core Jolt::Jolt)
692+
target_link_libraries(_culverin_c PRIVATE ${MAG_MUTEX_TARGET} ${JOLTC_TARGET} fast_capi_core Jolt::Jolt docs_embedder)
680693

681694
if(APPLE)
682695
message(STATUS "Culverin: Enforcing Strong Symbol Export")

extern/docs-embedder

Submodule docs-embedder added at 0e9a07d

src/culverin/culverin.c

Lines changed: 24 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
# define _CRT_SECURE_NO_WARNINGS
33
#endif
44

5-
#include "culverin_filters.h"
6-
#include "culverin_handler.h"
75
#include "culverin.h"
86
#include "culverin_arg_indices.h"
97
#include "culverin_character.h"
@@ -13,7 +11,9 @@
1311
#include "culverin_ecs.h"
1412
#include "culverin_fast_build.h"
1513
#include "culverin_fast_parse.h"
14+
#include "culverin_filters.h"
1615
#include "culverin_getters.h"
16+
#include "culverin_handler.h"
1717
#include "culverin_math.h"
1818
#include "culverin_parsers.h"
1919
#include "culverin_physics_sync.h"
@@ -24,6 +24,7 @@
2424
#include "culverin_shadow_sync.h"
2525
#include "culverin_soft_body.h"
2626
#include "culverin_vehicle.h"
27+
#include "docs_embedder.h"
2728
#include "joltc.h"
2829
#include <stdatomic.h>
2930

@@ -114,6 +115,7 @@ PyType_DeclareSlot_Status PhysicsWorld_init(PhysicsWorldObject *self, PyObject *
114115
"cannot be re-initialized.");
115116
return -1;
116117
}
118+
auto st = get_culverin_state(PyType_GetModule(Py_TYPE(self)));
117119
PyObject *settings_dict = nullptr;
118120
PyObject *bodies_list = nullptr;
119121
PyObject *baked = nullptr;
@@ -123,8 +125,10 @@ PyType_DeclareSlot_Status PhysicsWorld_init(PhysicsWorldObject *self, PyObject *
123125
int max_bodies;
124126
int max_pairs;
125127

126-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", (char *[]){"settings", "bodies", nullptr},
127-
&settings_dict, &bodies_list)) {
128+
void *targets[WorldInit_COUNT] = {[IDX_SETTINGS] = (void *)&settings_dict,
129+
[IDX_BODIES] = (void *)&bodies_list};
130+
131+
if (!FastParse_Unified(args, kwds, nullptr, &st->parsers.WorldInitParser, targets)) {
128132
return -1;
129133
}
130134

@@ -4030,138 +4034,6 @@ static const unsigned char ALL_DOCS[] = {
40304034
// Global flag to ensure we only stitch once (important for subinterpreters)
40314035
static atomic_int docs_status = 0;
40324036

4033-
static const char *COMMENT_MARKER = "<!--";
4034-
4035-
static char *allocate_docstring(const char *start, size_t length) {
4036-
char *doc = (char *)PyMem_RawMalloc(length + 1);
4037-
if (doc == nullptr) {
4038-
return nullptr;
4039-
}
4040-
memcpy(doc, start, length);
4041-
doc[length] = '\0';
4042-
return doc;
4043-
}
4044-
4045-
// Master Docstring Extractor (Handles Nested ## class -> ### method)
4046-
static char *extract_docstring(const char *class_name, const char *method_name) {
4047-
static constexpr size_t DOC_BUFFER = 128;
4048-
char class_key[DOC_BUFFER];
4049-
snprintf(class_key, sizeof(class_key), "## class %s", class_name);
4050-
4051-
// 1. Find the Class boundary
4052-
const char *docs_ptr = (const char *)ALL_DOCS;
4053-
char *class_start = strstr(docs_ptr, class_key);
4054-
if (!class_start) {
4055-
return nullptr;
4056-
}
4057-
4058-
// The scope of this class ends when the next class begins
4059-
char *class_end = strstr(class_start + 1, "## class ");
4060-
4061-
// 2. Find the Method boundary within this class
4062-
char method_key[DOC_BUFFER];
4063-
snprintf(method_key, sizeof(method_key), "### %s", method_name);
4064-
4065-
char *method_start = strstr(class_start, method_key);
4066-
4067-
// Ensure we found it AND it belongs to THIS class AND isn't a substring (like finding "step_up"
4068-
// for "step")
4069-
while (method_start && (class_end == nullptr || method_start < class_end)) {
4070-
char c = *(method_start + strlen(method_key));
4071-
// We allow '(', '\r', '\n', ' ', or '\0' after the method name
4072-
if (c == '(' || c == '\r' || c == '\n' || c == ' ' || c == '\0') {
4073-
break; // Valid match
4074-
}
4075-
method_start = strstr(method_start + 1, method_key); // Keep searching
4076-
}
4077-
4078-
if (!method_start || (class_end != nullptr && method_start >= class_end)) {
4079-
return nullptr;
4080-
}
4081-
4082-
// 3. Move past the "### method(...)\n" header
4083-
char *doc_start = method_start;
4084-
while (*doc_start != '\0' && *doc_start != '\n' && *doc_start != '\r') {
4085-
doc_start++;
4086-
}
4087-
while (*doc_start == '\n' || *doc_start == '\r') {
4088-
doc_start++;
4089-
}
4090-
4091-
// 4. Skip HTML Comments if present
4092-
if (strncmp(doc_start, COMMENT_MARKER, 4) == 0) {
4093-
char *comment_end = strstr(doc_start, "-->");
4094-
if (comment_end) {
4095-
doc_start = comment_end + 3; // Skip "-->"
4096-
while (*doc_start == '\n' || *doc_start == '\r' || *doc_start == ' ') {
4097-
doc_start++;
4098-
}
4099-
}
4100-
}
4101-
4102-
// 5. Find the end of this method's docs (next ### or ##)
4103-
char *doc_end = doc_start;
4104-
while (*doc_end != '\0') {
4105-
if (strncmp(doc_end, "### ", 4) == 0 || strncmp(doc_end, "## ", 3) == 0) {
4106-
break;
4107-
}
4108-
doc_end++;
4109-
}
4110-
4111-
// 6. Trim trailing whitespace
4112-
if (doc_end > doc_start) {
4113-
doc_end--;
4114-
while (doc_end > doc_start &&
4115-
(*doc_end == '\n' || *doc_end == '\r' || *doc_end == ' ' || *doc_end == '\t')) {
4116-
doc_end--;
4117-
}
4118-
}
4119-
4120-
if (doc_end >= doc_start) {
4121-
return allocate_docstring(doc_start, (size_t)(doc_end - doc_start + 1));
4122-
}
4123-
4124-
return nullptr;
4125-
}
4126-
4127-
// Pass 1: Stitch docstrings to PyMethodDefs
4128-
static void stitch_docs(PyMethodDef *methods, const char *class_name) {
4129-
if (methods == nullptr) {
4130-
return;
4131-
}
4132-
for (PyMethodDef *m = methods; m->ml_name != nullptr; m++) {
4133-
if (m->ml_doc == nullptr) {
4134-
m->ml_doc = extract_docstring(class_name, m->ml_name);
4135-
}
4136-
}
4137-
}
4138-
4139-
// Pass 2: Stitch docstrings into PyGetSetDef getters
4140-
static void stitch_docs_getset(PyGetSetDef *getset, const char *class_name) {
4141-
if (getset == nullptr) {
4142-
return;
4143-
}
4144-
for (PyGetSetDef *g = getset; g->name != nullptr; g++) {
4145-
if (g->doc == nullptr) {
4146-
g->doc = extract_docstring(class_name, g->name);
4147-
}
4148-
}
4149-
}
4150-
4151-
static void stitch_spec(PyType_Spec *spec, const char *class_name) {
4152-
if (!spec || !spec->slots) {
4153-
return;
4154-
}
4155-
// Cast to PyType_Slot* to iterate
4156-
for (const PyType_Slot *slot = (const PyType_Slot *)spec->slots; slot->slot != 0; slot++) {
4157-
if (slot->slot == Py_tp_methods) {
4158-
stitch_docs((PyMethodDef *)slot->pfunc, class_name);
4159-
} else if (slot->slot == Py_tp_getset) {
4160-
stitch_docs_getset((PyGetSetDef *)slot->pfunc, class_name);
4161-
}
4162-
}
4163-
}
4164-
41654037
// =============================================================================================
41664038

41674039
// --- Macros ---
@@ -4490,17 +4362,17 @@ static PyType_Spec Registry_spec = {
44904362
REG_NOARGS(create),
44914363
REG_FASTCALL(destroy),
44924364
REG_FASTCALL(is_alive),
4493-
REG_NOARGS(clear), // Wipes the registry
4365+
REG_NOARGS(clear), // Wipes the registry
44944366
REG_FASTCALL(register_component),
44954367
REG_FASTCALL(add),
44964368
REG_FASTCALL(remove),
44974369
REG_FASTCALL(has),
4498-
REG_FASTCALL(get), // Single entity data access
4370+
REG_FASTCALL(get), // Single entity data access
44994371
REG_FASTCALL(get_view),
45004372
REG_FASTCALL(get_entities),
45014373
REG_FASTCALL(sync_from_world),
4502-
REG_NOARGS(get_active_count), // ECS Statistics
4503-
REG_FASTCALL(get_component_count), // ECS Statistics
4374+
REG_NOARGS(get_active_count), // ECS Statistics
4375+
REG_FASTCALL(get_component_count), // ECS Statistics
45044376
{}
45054377

45064378
}},
@@ -4710,17 +4582,18 @@ PyType_DeclareSlot_Status culverin_exec(PyObject *m) {
47104582
precision, build_type, compiler_id);
47114583

47124584
// --- THE WINNER: Run exactly once per process life ---
4713-
stitch_docs(culverin_module.m_methods, "Module");
4714-
stitch_spec(&PhysicsWorld_spec, "PhysicsWorld");
4715-
stitch_spec(&Character_spec, "Character");
4716-
stitch_spec(&Vehicle_spec, "Vehicle");
4717-
stitch_spec(&Skeleton_spec, "Skeleton");
4718-
stitch_spec(&Ragdoll_spec, "Ragdoll");
4719-
stitch_spec(&RagdollSettings_spec, "RagdollSettings");
4720-
stitch_spec(&SoftBodySharedSettings_spec, "SoftBodySharedSettings");
4721-
stitch_spec(&Registry_spec, "Registry");
4722-
stitch_spec(&MathService_spec, "MathService");
4723-
stitch_spec(&Ship_spec, "Ship");
4585+
const char *docs_str = (const char *)ALL_DOCS;
4586+
md_stitch_methods(culverin_module.m_methods, "Module", docs_str);
4587+
md_stitch_spec(&PhysicsWorld_spec, "PhysicsWorld", docs_str);
4588+
md_stitch_spec(&Character_spec, "Character", docs_str);
4589+
md_stitch_spec(&Vehicle_spec, "Vehicle", docs_str);
4590+
md_stitch_spec(&Skeleton_spec, "Skeleton", docs_str);
4591+
md_stitch_spec(&Ragdoll_spec, "Ragdoll", docs_str);
4592+
md_stitch_spec(&RagdollSettings_spec, "RagdollSettings", docs_str);
4593+
md_stitch_spec(&SoftBodySharedSettings_spec, "SoftBodySharedSettings", docs_str);
4594+
md_stitch_spec(&Registry_spec, "Registry", docs_str);
4595+
md_stitch_spec(&MathService_spec, "MathService", docs_str);
4596+
md_stitch_spec(&Ship_spec, "Ship", docs_str);
47244597

47254598
// Gated Handler Registration
47264599
JPH_SetTraceHandler(culv_jph_trace);

src/culverin/culverin_arg_indices.h

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ static constexpr size_t PARSER_REGISTRY_SIZE = 128;
1111
* Format: X(IndexName, PythonName, C-Type, IsRequired)
1212
*/
1313

14+
#define SCHEMA_WORLD_INIT(X) \
15+
X(IDX_SETTINGS, "settings", PyObject *, false) \
16+
X(IDX_BODIES, "bodies", PyObject *, false)
17+
1418
#define SCHEMA_BODY(X) \
1519
X(IDX_POS, "pos", PyObject *, false) \
1620
X(IDX_ROT, "rot", PyObject *, false) \
@@ -552,23 +556,24 @@ static constexpr size_t PARSER_REGISTRY_SIZE = 128;
552556
X(IDX_35, "a35", uint64_t, false) \
553557
X(IDX_36, "a36", uint64_t, false) \
554558
X(IDX_37, "a37", uint64_t, 0) \
555-
X(IDX_38, "a38", uint64_t, false) X(IDX_39, "a39", uint64_t, false) \
556-
X(IDX_40, "a40", uint64_t, false) X(IDX_41, "a41", uint64_t, 0) \
557-
X(IDX_42, "a42", uint64_t, false) X(IDX_43, "a43", uint64_t, false) \
558-
X(IDX_44, "a44", uint64_t, false) X(IDX_45, "a45", uint64_t, 0) \
559-
X(IDX_46, "a46", uint64_t, false) X(IDX_47, "a47", uint64_t, 0) \
560-
X(IDX_48, "a48", uint64_t, 0) X(IDX_49, "a49", uint64_t, 0) \
561-
X(IDX_50, "a50", uint64_t, 0) X(IDX_51, "a51", uint64_t, false) \
562-
X(IDX_52, "a52", uint64_t, 0) X(IDX_53, "a53", uint64_t, 0) \
563-
X(IDX_54, "a54", uint64_t, 0) X(IDX_55, "a55", uint64_t, 0) \
564-
X(IDX_56, "a56", uint64_t, false) \
565-
X(IDX_57, "a57", uint64_t, 0) X(IDX_58, "a58", \
566-
uint64_t, false) \
567-
X(IDX_59, "a59", uint64_t, false) \
568-
X(IDX_60, "a60", uint64_t, false) \
569-
X(IDX_61, "a61", uint64_t, false) \
570-
X(IDX_62, "a62", uint64_t, false) \
571-
X(IDX_63, "a63", uint64_t, false)
559+
X(IDX_38, "a38", uint64_t, false) \
560+
X(IDX_39, "a39", uint64_t, false) \
561+
X(IDX_40, "a40", uint64_t, false) \
562+
X(IDX_41, "a41", uint64_t, 0) X(IDX_42, "a42", uint64_t, false) \
563+
X(IDX_43, "a43", uint64_t, false) X(IDX_44, "a44", uint64_t, false) \
564+
X(IDX_45, "a45", uint64_t, 0) X(IDX_46, "a46", uint64_t, false) \
565+
X(IDX_47, "a47", uint64_t, 0) X(IDX_48, "a48", uint64_t, 0) \
566+
X(IDX_49, "a49", uint64_t, 0) X(IDX_50, "a50", uint64_t, 0) \
567+
X(IDX_51, "a51", uint64_t, false) X(IDX_52, "a52", uint64_t, 0) \
568+
X(IDX_53, "a53", uint64_t, 0) X(IDX_54, "a54", uint64_t, 0) \
569+
X(IDX_55, "a55", uint64_t, 0) X(IDX_56, "a56", uint64_t, false) \
570+
X(IDX_57, "a57", uint64_t, 0) \
571+
X(IDX_58, "a58", uint64_t, false) \
572+
X(IDX_59, "a59", uint64_t, false) \
573+
X(IDX_60, "a60", uint64_t, false) \
574+
X(IDX_61, "a61", uint64_t, false) \
575+
X(IDX_62, "a62", uint64_t, false) \
576+
X(IDX_63, "a63", uint64_t, false)
572577

573578
/** --- THE GENERATOR ENGINE --- **/
574579

@@ -584,6 +589,7 @@ static constexpr size_t PARSER_REGISTRY_SIZE = 128;
584589
FastArgSpec ParserName##Specs[GroupName##_COUNT];
585590

586591
// A. Define the Index Groups (One per unique signature)
592+
DEFINE_INDEX_GROUP(WorldInit, SCHEMA_WORLD_INIT)
587593
DEFINE_INDEX_GROUP(Body, SCHEMA_BODY)
588594
DEFINE_INDEX_GROUP(Vec3, SCHEMA_VEC3)
589595
DEFINE_INDEX_GROUP(ImpAt, SCHEMA_IMPULSE_AT)
@@ -675,6 +681,7 @@ DEFINE_INDEX_GROUP(ShipInput, SCHEMA_SHIP_INPUT)
675681
DEFINE_INDEX_GROUP(StressTest, SCHEMA_STRESS_TEST)
676682

677683
#define FOR_ALL_PARSERS(X) \
684+
X(WorldInit, WorldInit, SCHEMA_WORLD_INIT) \
678685
X(Body, Body, SCHEMA_BODY) \
679686
X(Impulse, Vec3, SCHEMA_VEC3) \
680687
X(WheelIdx, WheelIdx, SCHEMA_WHEEL_IDX) \

0 commit comments

Comments
 (0)