code wiki / (root) / nx_game_page_sink_t221.nx

nx_game_page_sink_t221.nx source

↩ module page · 153 lines · 7188 B

1// ORIGINAL proprietary. Owned serialization dependency for the existing game-page emitter. 2// See knowledge/gates/page-owned-sink-design-t221.md. Primitive qualification only; 3// the current page template and its raw-buffer callers are not migrated yet. 4import "nx_buf_dyn.nx" 5import "nx_itoa_lib.nx" 6import "nx_base64.nx" 7 8struct GpeSink { bytes:*NxBufOwned, error:i64, max_bytes:i64, scratch:*u8, scratch_cap:i64, } 9const GPE_SINK_RECORD_BYTES:i64=__size_of(GpeSink)+NX_BO_BYTES 10const GPE_SINK_I64_MIN:i64=0-NX_BO_I64_MAX-1 11const GPE_SINK_BASE64_INPUT_QUANTUM:i64=3 12const GPE_SINK_BASE64_OUTPUT_QUANTUM:i64=4 13 14// One allocation owns both descriptors; byte storage is exclusively NxBufOwned's. 15func gpe_sink_new(max_bytes:i64)->*GpeSink{ 16 if max_bytes<0{return 0 as *GpeSink} 17 let s:*GpeSink=sys_mmap_try(GPE_SINK_RECORD_BYTES) as *GpeSink 18 if (s as i64)<=0{return 0 as *GpeSink} 19 s.bytes=(s as i64+__size_of(GpeSink)) as *NxBufOwned 20 s.bytes.buf=0 as *u8;s.bytes.len=0;s.bytes.cap=0 21 s.error=0;s.max_bytes=max_bytes;s.scratch=0 as *u8;s.scratch_cap=0;return s 22} 23func gpe_sink_fail(s:*GpeSink,rc:i64)->i64{if s.error==0{s.error=rc};return s.error} 24func gpe_sink_ready(s:*GpeSink)->i64{ 25 if (s as i64)<=0{return NX_BO_INVALID} 26 if s.error<0{return s.error} 27 let valid:i64=nx_bo_state(s.bytes) 28 if valid<0{return gpe_sink_fail(s,valid)} 29 if s.max_bytes<0{return gpe_sink_fail(s,NX_BO_INVALID)} 30 return 0 31} 32func gpe_sink_room(s:*GpeSink,extra:i64)->i64{ 33 let ready:i64=gpe_sink_ready(s);if ready<0{return ready} 34 if extra<0{return gpe_sink_fail(s,NX_BO_INVALID)} 35 if extra>NX_BO_I64_MAX-s.bytes.len{return gpe_sink_fail(s,NX_BO_RANGE)} 36 if s.max_bytes>0&&extra>s.max_bytes-s.bytes.len{return gpe_sink_fail(s,NX_BO_CAPACITY)} 37 return 0 38} 39// Source storage must remain live and immutable for the duration of the call. 40func gpe_sink_source(s:*GpeSink,src:*u8,n:i64)->i64{ 41 let ready:i64=gpe_sink_ready(s);if ready<0{return ready} 42 if n<0{return gpe_sink_fail(s,NX_BO_INVALID)};if n==0{return 0} 43 let sp:i64=src as i64 44 if sp<=0{return gpe_sink_fail(s,NX_BO_INVALID)} 45 if n>NX_BO_I64_MAX-sp{return gpe_sink_fail(s,NX_BO_RANGE)} 46 let owner:i64=s as i64 47 if sp<owner+GPE_SINK_RECORD_BYTES&&owner<sp+n{return gpe_sink_fail(s,NX_BO_ALIAS)} 48 let output:i64=s.bytes.buf as i64 49 if s.bytes.cap>0&&sp<output+s.bytes.cap&&output<sp+n{return gpe_sink_fail(s,NX_BO_ALIAS)} 50 return 0 51} 52func gpe_sink_span(s:*GpeSink,src:*u8,n:i64)->i64{ 53 let source:i64=gpe_sink_source(s,src,n);if source<0{return source} 54 let rc:i64=nx_bo_append(s.bytes,src,n,s.max_bytes) 55 if rc<0{return gpe_sink_fail(s,rc)};return s.bytes.len 56} 57func gpe_sink_byte(s:*GpeSink,value:i64)->i64{ 58 let ready:i64=gpe_sink_ready(s);if ready<0{return ready} 59 if value<0||value>255{return gpe_sink_fail(s,NX_BO_INVALID)} 60 let room:i64=gpe_sink_room(s,1);if room<0{return room} 61 let allocated:i64=gpe_sink_scratch_new(s,1);if allocated<0{return allocated} 62 s.scratch[0]=value as u8;return gpe_sink_scratch_commit(s,1) 63} 64// This bounded string adapter requires a NUL inside the caller-declared source span. 65func gpe_sink_text(s:*GpeSink,text:*u8,available:i64)->i64{ 66 let source:i64=gpe_sink_source(s,text,available);if source<0{return source} 67 var n:i64=0 68 while n<available{if text[n]==(0 as u8){return gpe_sink_span(s,text,n)};n=n+1} 69 return gpe_sink_fail(s,NX_BO_INVALID) 70} 71func gpe_sink_num(s:*GpeSink,value:i64)->i64{ 72 let ready:i64=gpe_sink_ready(s);if ready<0{return ready} 73 // The existing shared integer formatter deliberately clamps MIN; retain its exact representation here. 74 if value==GPE_SINK_I64_MIN{return gpe_sink_span(s,"-9223372036854775808",20)} 75 // Retained native qualification found address-taken stack scratch corrupted by later locals. 76 // Use the sink's owned scratch until that compiler path is separately repaired and qualified. 77 let allocated:i64=gpe_sink_scratch_new(s,NXI_BUF);if allocated<0{return allocated} 78 let n:i64=nxi_buf(s.scratch,0,value) 79 return gpe_sink_scratch_commit(s,n) 80} 81func gpe_sink_scratch_release(s:*GpeSink)->i64{ 82 if s.scratch_cap==0{return 0} 83 let rc:i64=sys_munmap_direct(s.scratch,s.scratch_cap) 84 if rc<0{return gpe_sink_fail(s,NX_BO_RELEASE)} 85 s.scratch=0 as *u8;s.scratch_cap=0;return 0 86} 87func gpe_sink_scratch_new(s:*GpeSink,capacity:i64)->i64{ 88 let ready:i64=gpe_sink_ready(s);if ready<0{return ready} 89 if capacity<=0||s.scratch_cap!=0{return gpe_sink_fail(s,NX_BO_INVALID)} 90 let p:*u8=sys_mmap_try(capacity) 91 if (p as i64)<=0{return gpe_sink_fail(s,NX_BO_ALLOCATION)} 92 s.scratch=p;s.scratch_cap=capacity;return 0 93} 94func gpe_sink_scratch_commit(s:*GpeSink,n:i64)->i64{ 95 if n<0||n>s.scratch_cap{return gpe_sink_fail(s,NX_BO_RANGE)} 96 let wrote:i64=gpe_sink_span(s,s.scratch,n) 97 let released:i64=gpe_sink_scratch_release(s) 98 if wrote<0{return wrote};if released<0{return released};return s.bytes.len 99} 100// Compatibility with rk_js_str: escape LF, quote and backslash; omit CR. 101// Encoding/semantic validation remains the input owner's responsibility. 102func gpe_sink_js(s:*GpeSink,src:*u8,n:i64)->i64{ 103 let source:i64=gpe_sink_source(s,src,n);if source<0{return source} 104 var size:i64=2;var i:i64=0 105 while i<n{ 106 let c:i64=src[i] as i64;var extra:i64=1 107 if c==13{extra=0}else{if c==10||c==34||c==92{extra=2}} 108 if extra>NX_BO_I64_MAX-size{return gpe_sink_fail(s,NX_BO_RANGE)} 109 size=size+extra;i=i+1 110 } 111 let room:i64=gpe_sink_room(s,size);if room<0{return room} 112 let allocated:i64=gpe_sink_scratch_new(s,size);if allocated<0{return allocated} 113 let dest:*u8=s.scratch;var p:i64=0;dest[p]=34 as u8;p=p+1;i=0 114 while i<n{ 115 let c:i64=src[i] as i64 116 if c==10{dest[p]=92 as u8;dest[p+1]=110 as u8;p=p+2} 117 else{if c==34||c==92{dest[p]=92 as u8;dest[p+1]=c as u8;p=p+2} 118 else{if c!=13{dest[p]=c as u8;p=p+1}}} 119 i=i+1 120 } 121 dest[p]=34 as u8;p=p+1 122 return gpe_sink_scratch_commit(s,p) 123} 124func gpe_sink_base64_size(n:i64)->i64{ 125 if n<0{return NX_BO_INVALID} 126 var groups:i64=n/GPE_SINK_BASE64_INPUT_QUANTUM 127 if n%GPE_SINK_BASE64_INPUT_QUANTUM!=0{groups=groups+1} 128 if groups>NX_BO_I64_MAX/GPE_SINK_BASE64_OUTPUT_QUANTUM{return NX_BO_RANGE} 129 return groups*GPE_SINK_BASE64_OUTPUT_QUANTUM 130} 131func gpe_sink_base64(s:*GpeSink,src:*u8,n:i64)->i64{ 132 let source:i64=gpe_sink_source(s,src,n);if source<0{return source} 133 let size:i64=gpe_sink_base64_size(n);if size<0{return gpe_sink_fail(s,size)} 134 let room:i64=gpe_sink_room(s,size);if room<0{return room} 135 if size==0{return s.bytes.len} 136 let allocated:i64=gpe_sink_scratch_new(s,size);if allocated<0{return allocated} 137 let wrote:i64=b64_encode(src,n,s.scratch) 138 if wrote!=size{gpe_sink_fail(s,NX_BO_RANGE);gpe_sink_scratch_release(s);return s.error} 139 return gpe_sink_scratch_commit(s,wrote) 140} 141// Release is repeatable and retries any retained scratch cleanup; error remains latched. 142func gpe_sink_release(s:*GpeSink)->i64{ 143 if (s as i64)<=0{return NX_BO_INVALID} 144 let scratch:i64=gpe_sink_scratch_release(s) 145 let bytes:i64=nx_bo_release(s.bytes) 146 if bytes<0{gpe_sink_fail(s,bytes)} 147 if scratch<0{return scratch};return bytes 148} 149// Consumes the handle only on success; caller must clear its pointer after success. 150func gpe_sink_destroy(s:*GpeSink)->i64{ 151 let rc:i64=gpe_sink_release(s);if rc<0{return rc} 152 return sys_munmap_direct(s as *u8,GPE_SINK_RECORD_BYTES) 153}