code wiki / _hdl_build / nx_skill_video.nx
nx_skill_video.nx source
↩ module page · 248 lines · 9428 B
1// nx_skill_video.nx -- LIB: WHO you learn a technique from is the learner's choice, not ours.
2//
3// For any skill the coach teaches, there may be several ways to watch it done: a PROFESSIONAL source, an
4// AMATEUR one (often better -- a home cook's kitchen looks like yours), or a specific CHEF the learner already
5// follows. This registry holds those sources per skill, keeps the chefs a person follows, and answers the
6// question that actually gets asked: "has the cook I like already covered this?"
7//
8// THAT QUESTION IS A TRI-STATE, ON PURPOSE. sv_chef_covers returns -1 for a chef we have never heard of, 0 for
9// a chef we know who has not covered this skill, and 1 for covered. Collapsing the first two into "no" would
10// tell someone their favourite chef has not made the video when the truth is we never indexed them -- and that
11// wrong answer is exactly what would send a pointless request to a chef who already has one.
12//
13// LINK ROT IS THE SAME PROBLEM AS THE RECIPE. A technique video is a citation like any other, so this reuses
14// ms_is_archived from nx_meal_source rather than growing a second archive predicate: a source renders as
15// preserved ONLY when its bytes actually come back out of the WARC.
16//
17// Schema (prefix passed in, e.g. knowledge/store/skillvid-):
18// svid:ids -> TAB list of video ids
19// svid:<vid> -> skill <t> title <t> creator <t> kind <t> url <t> licence <t> seconds <t> chef-id
20// svid:skill:<sid> -> TAB list of video ids teaching that skill
21// chef:ids -> TAB list of chef ids
22// chef:<cid> -> display name <t> handle <t> home url
23// follow:<unit> -> TAB list of chef ids this unit follows
24// license_tier: ORIGINAL No hw writes (Rule 26).
25import "nx_meal_plan.nx"
26import "nx_meal_source.nx"
27import "nx_food_science.nx"
28import "nx_seg_store.nx"
29import "nx_syscalls.nx"
30
31const SV_TAB: i64 = 9
32const SV_KEYCAP: i64 = 200
33const SV_VALCAP: i64 = 2048
34const SV_MAXV: i64 = 256
35
36// svid:<vid> fields
37const SV_F_SKILL: i64 = 0
38const SV_F_TITLE: i64 = 1
39const SV_F_CREATOR: i64 = 2
40const SV_F_KIND: i64 = 3
41const SV_F_URL: i64 = 4
42const SV_F_LICENSE: i64 = 5
43const SV_F_SECONDS: i64 = 6
44const SV_F_CHEF: i64 = 7
45
46// chef:<cid> fields
47const SV_C_NAME: i64 = 0
48const SV_C_HANDLE: i64 = 1
49const SV_C_HOME: i64 = 2
50
51// sv_chef_covers outcomes -- three distinct facts, never two
52const SV_CHEF_UNKNOWN: i64 = 0 - 1
53const SV_CHEF_NO: i64 = 0
54const SV_CHEF_YES: i64 = 1
55
56static SV_KB: i64
57static SV_PQ: i64
58static SV_LQ: i64
59func sv_keybuf() -> *u8 { if SV_KB == 0 { SV_KB = sys_mmap(SV_KEYCAP) as i64 } return SV_KB as *u8 }
60func sv_pq() -> *i64 { if SV_PQ == 0 { SV_PQ = sys_mmap(16) as i64 } return SV_PQ as *i64 }
61func sv_lq() -> *i64 { if SV_LQ == 0 { SV_LQ = sys_mmap(16) as i64 } return SV_LQ as *i64 }
62func sv_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
63
64static SV_EMPTY: i64
65func sv_empty() -> *u8 {
66 if SV_EMPTY == 0 { SV_EMPTY = sys_mmap(8) as i64 }
67 let b: *u8 = SV_EMPTY as *u8
68 b[0] = 0 as u8
69 return b
70}
71
72func sv_field_at(prefix: *u8, key: *u8, f: i64, out: *u8) -> i64 {
73 let pq: *i64 = sv_pq()
74 let lq: *i64 = sv_lq()
75 if ss_get(prefix, key, pq, lq) != 1 { out[0] = 0 as u8; return 0 }
76 return fd_field(pq[0] as *u8, lq[0], f, out)
77}
78
79// ---- videos ----
80
81func sv_vid_key(vid: *u8, out: *u8) -> i64 {
82 var o: i64 = as_append(out, 0, "svid:" as *u8)
83 o = as_append(out, o, vid)
84 out[o] = 0 as u8
85 return o
86}
87func sv_skill_key(sid: *u8, out: *u8) -> i64 {
88 var o: i64 = as_append(out, 0, "svid:skill:" as *u8)
89 o = as_append(out, o, sid)
90 out[o] = 0 as u8
91 return o
92}
93
94// kind is "pro" | "amateur" | "chef". chef is the chef id for kind=chef, else an empty buffer.
95func sv_add_video(prefix: *u8, vid: *u8, sid: *u8, title: *u8, creator: *u8, kind: *u8, url: *u8, license: *u8, seconds: i64, chef: *u8) -> i64 {
96 let key: *u8 = sys_mmap(SV_KEYCAP)
97 sv_vid_key(vid, key)
98 let val: *u8 = sys_mmap(SV_VALCAP)
99 var o: i64 = 0
100 o = as_append(val, o, sid); val[o] = SV_TAB as u8; o = o + 1
101 o = as_append(val, o, title); val[o] = SV_TAB as u8; o = o + 1
102 o = as_append(val, o, creator); val[o] = SV_TAB as u8; o = o + 1
103 o = as_append(val, o, kind); val[o] = SV_TAB as u8; o = o + 1
104 o = as_append(val, o, url); val[o] = SV_TAB as u8; o = o + 1
105 o = as_append(val, o, license); val[o] = SV_TAB as u8; o = o + 1
106 o = fd_apnum(val, o, seconds); val[o] = SV_TAB as u8; o = o + 1
107 o = as_append(val, o, chef)
108 val[o] = 0 as u8
109 let w: i64 = mp_put(prefix, key, val)
110 mp_list_add(prefix, "svid:ids" as *u8, vid)
111 let sk: *u8 = sys_mmap(SV_KEYCAP)
112 sv_skill_key(sid, sk)
113 mp_list_add(prefix, sk, vid)
114 return w
115}
116
117func sv_field(prefix: *u8, vid: *u8, f: i64, out: *u8) -> i64 {
118 let key: *u8 = sv_keybuf()
119 sv_vid_key(vid, key)
120 return sv_field_at(prefix, key, f, out)
121}
122
123func sv_seconds(prefix: *u8, vid: *u8) -> i64 {
124 let b: *u8 = sys_mmap(64)
125 if sv_field(prefix, vid, SV_F_SECONDS, b) == 0 { return 0 - 1 }
126 return fd_atoi(b, sv_len(b))
127}
128
129// every video teaching a skill; returns the count.
130func sv_by_skill(prefix: *u8, sid: *u8, outtoks: *i64) -> i64 {
131 let key: *u8 = sv_keybuf()
132 sv_skill_key(sid, key)
133 let pq: *i64 = sv_pq()
134 let lq: *i64 = sv_lq()
135 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
136 return mp_split(pq[0] as *u8, lq[0], SV_TAB, outtoks)
137}
138
139// the learner's filter: only professional sources, only amateur ones, or only a followed chef's.
140func sv_by_skill_kind(prefix: *u8, sid: *u8, kind: *u8, outtoks: *i64) -> i64 {
141 let all: *i64 = sys_mmap(8 * SV_MAXV) as *i64
142 let n: i64 = sv_by_skill(prefix, sid, all)
143 let k: *u8 = sys_mmap(64)
144 var c: i64 = 0
145 var i: i64 = 0
146 while i < n {
147 let vid: *u8 = all[i] as *u8
148 if sv_field(prefix, vid, SV_F_KIND, k) > 0 {
149 if fd_streq(k, kind) == 1 { outtoks[c] = vid as i64; c = c + 1 }
150 }
151 i = i + 1
152 }
153 return c
154}
155
156// ---- chefs, and who a learner follows ----
157
158func sv_chef_key(cid: *u8, out: *u8) -> i64 {
159 var o: i64 = as_append(out, 0, "chef:" as *u8)
160 o = as_append(out, o, cid)
161 out[o] = 0 as u8
162 return o
163}
164func sv_follow_key(unit: *u8, out: *u8) -> i64 {
165 var o: i64 = as_append(out, 0, "follow:" as *u8)
166 o = as_append(out, o, unit)
167 out[o] = 0 as u8
168 return o
169}
170
171func sv_add_chef(prefix: *u8, cid: *u8, name: *u8, handle: *u8, home: *u8) -> i64 {
172 let key: *u8 = sys_mmap(SV_KEYCAP)
173 sv_chef_key(cid, key)
174 let val: *u8 = sys_mmap(SV_VALCAP)
175 var o: i64 = 0
176 o = as_append(val, o, name); val[o] = SV_TAB as u8; o = o + 1
177 o = as_append(val, o, handle); val[o] = SV_TAB as u8; o = o + 1
178 o = as_append(val, o, home)
179 val[o] = 0 as u8
180 let w: i64 = mp_put(prefix, key, val)
181 mp_list_add(prefix, "chef:ids" as *u8, cid)
182 return w
183}
184
185func sv_chef_field(prefix: *u8, cid: *u8, f: i64, out: *u8) -> i64 {
186 let key: *u8 = sv_keybuf()
187 sv_chef_key(cid, key)
188 return sv_field_at(prefix, key, f, out)
189}
190
191func sv_chef_known(prefix: *u8, cid: *u8) -> i64 {
192 let b: *u8 = sys_mmap(SV_VALCAP)
193 if sv_chef_field(prefix, cid, SV_C_NAME, b) == 0 { return 0 }
194 return 1
195}
196
197func sv_follow(prefix: *u8, unit: *u8, cid: *u8) -> i64 {
198 let key: *u8 = sys_mmap(SV_KEYCAP)
199 sv_follow_key(unit, key)
200 return mp_list_add(prefix, key, cid)
201}
202
203func sv_follows(prefix: *u8, unit: *u8, outtoks: *i64) -> i64 {
204 let key: *u8 = sv_keybuf()
205 sv_follow_key(unit, key)
206 let pq: *i64 = sv_pq()
207 let lq: *i64 = sv_lq()
208 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
209 return mp_split(pq[0] as *u8, lq[0], SV_TAB, outtoks)
210}
211
212// THE QUESTION PEOPLE ACTUALLY ASK: has the cook I follow covered this?
213// SV_CHEF_UNKNOWN -- we have never indexed this chef; we do not know, and must not say no
214// SV_CHEF_NO -- we know them, and they have not covered this skill (a legitimate request)
215// SV_CHEF_YES -- covered; the video id lands in out_vid
216func sv_chef_covers(prefix: *u8, cid: *u8, sid: *u8, out_vid: *i64) -> i64 {
217 if sv_chef_known(prefix, cid) == 0 { return SV_CHEF_UNKNOWN }
218 let all: *i64 = sys_mmap(8 * SV_MAXV) as *i64
219 let n: i64 = sv_by_skill(prefix, sid, all)
220 let c: *u8 = sys_mmap(SV_KEYCAP)
221 var i: i64 = 0
222 while i < n {
223 let vid: *u8 = all[i] as *u8
224 if sv_field(prefix, vid, SV_F_CHEF, c) > 0 {
225 if fd_streq(c, cid) == 1 { out_vid[0] = vid as i64; return SV_CHEF_YES }
226 }
227 i = i + 1
228 }
229 out_vid[0] = 0
230 return SV_CHEF_NO
231}
232
233// ---- link rot, reusing the recipe's own predicate ----
234
235// 1 only when this video's URL actually resolves to preserved bytes. A surface may render "archived copy"
236// ONLY on a 1 -- the same rule the recipe credit strip follows, and the same function enforcing it.
237func sv_is_preserved(prefix: *u8, vid: *u8, warc: *u8, warclen: i64) -> i64 {
238 let u: *u8 = sys_mmap(SV_VALCAP)
239 if sv_field(prefix, vid, SV_F_URL, u) == 0 { return 0 }
240 return ms_is_archived(warc, warclen, u)
241}
242
243// preserved byte count for a video source, or -1 when nothing is held.
244func sv_preserved_bytes(prefix: *u8, vid: *u8, warc: *u8, warclen: i64) -> i64 {
245 let u: *u8 = sys_mmap(SV_VALCAP)
246 if sv_field(prefix, vid, SV_F_URL, u) == 0 { return 0 - 1 }
247 return ms_archive_bytes(warc, warclen, u)
248}