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"
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"
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)
40314035static 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 );
0 commit comments