File Coverage

Tlaloc.xs
Criterion Covered Total %
statement 389 509 76.4
branch 145 302 48.0
condition n/a
subroutine n/a
pod n/a
total 534 811 65.8


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include "ppport.h"
6              
7             /* ------------------------------------------------------------------ */
8             /* Custom op forward declarations (5.14+ only) */
9             /* ------------------------------------------------------------------ */
10              
11             #if PERL_VERSION >= 14
12             static OP *pp_tlaloc_wet(pTHX);
13             static OP *pp_tlaloc_drench(pTHX);
14             static OP *pp_tlaloc_dry(pTHX);
15             static OP *pp_tlaloc_wetness(pTHX);
16             static OP *pp_tlaloc_is_wet(pTHX);
17             static OP *pp_tlaloc_is_dry(pTHX);
18             static OP *pp_tlaloc_evap_rate(pTHX);
19             static XOP tlaloc_xop_wet;
20             static XOP tlaloc_xop_drench;
21             static XOP tlaloc_xop_dry;
22             static XOP tlaloc_xop_wetness;
23             static XOP tlaloc_xop_is_wet;
24             static XOP tlaloc_xop_is_dry;
25             static XOP tlaloc_xop_evap_rate;
26             #endif
27              
28             /* ------------------------------------------------------------------ */
29             /* Constants and struct */
30             /* ------------------------------------------------------------------ */
31              
32             #define EVAP_STEP_DEFAULT 10
33             #define WETNESS_MAX 100
34              
35             typedef struct {
36             int wetness; /* 0–100, decremented on each access via mg_get */
37             int evap_step; /* amount to decrement per access (default 10) */
38             } wetness_magic_t;
39              
40             /* ------------------------------------------------------------------ */
41             /* Forward declaration — vtable referenced by callbacks below */
42             /* ------------------------------------------------------------------ */
43              
44             static MGVTBL wetness_vtbl;
45              
46             /* ------------------------------------------------------------------ */
47             /* MGVTBL callbacks */
48             /* ------------------------------------------------------------------ */
49              
50             /* svt_get: fires on every Perl-level read of the scalar */
51             static int
52 42           wetness_mg_get(pTHX_ SV *sv, MAGIC *mg) {
53 42           wetness_magic_t *wm = (wetness_magic_t *)mg->mg_ptr;
54 42 50         if (wm) {
55 42           wm->wetness -= wm->evap_step;
56 42 50         if (wm->wetness < 0) wm->wetness = 0;
57             }
58 42           return 0;
59             }
60              
61             /* svt_free: fires when the SV is garbage-collected */
62             static int
63 197           wetness_mg_free(pTHX_ SV *sv, MAGIC *mg) {
64 197           wetness_magic_t *wm = (wetness_magic_t *)mg->mg_ptr;
65 197 50         if (wm) {
66 197           Safefree(wm);
67 197           mg->mg_ptr = NULL;
68             }
69 197           return 0;
70             }
71              
72             /* ------------------------------------------------------------------ */
73             /* Static vtable definition */
74             /* ------------------------------------------------------------------ */
75              
76             static MGVTBL wetness_vtbl = {
77             wetness_mg_get, /* svt_get */
78             NULL, /* svt_set */
79             NULL, /* svt_len */
80             NULL, /* svt_clear */
81             wetness_mg_free, /* svt_free */
82             NULL, /* svt_copy */
83             NULL, /* svt_dup */
84             NULL /* svt_local */
85             };
86              
87             /* ------------------------------------------------------------------ */
88             /* Helper functions */
89             /* ------------------------------------------------------------------ */
90              
91             /* Find our magic on an SV, keyed by vtable address not just type.
92             SvMAGIC is only valid for SVt_PVMG+; return NULL for smaller types. */
93             static MAGIC *
94 420           tlaloc_find_magic(pTHX_ SV *sv) {
95 420 100         if (SvTYPE(sv) < SVt_PVMG) return NULL;
96 351           return mg_findext(sv, PERL_MAGIC_ext, &wetness_vtbl);
97             }
98              
99             /* Remove our magic (triggers mg_free -> Safefree) */
100             static void
101 179           tlaloc_remove_magic(pTHX_ SV *sv) {
102 179           sv_unmagicext(sv, PERL_MAGIC_ext, &wetness_vtbl);
103 179           }
104              
105             /* Attach magic at add_level with evap_step, or top-up if already wet (capped at WETNESS_MAX) */
106             /* evap_step of -1 means "use default or keep existing" */
107             static void
108 205           tlaloc_attach_magic(pTHX_ SV *sv, int add_level, int evap_step) {
109 205           MAGIC *mg = tlaloc_find_magic(aTHX_ sv);
110 211 100         if (mg && mg->mg_ptr) {
    50          
111 6           wetness_magic_t *wm = (wetness_magic_t *)mg->mg_ptr;
112 6           wm->wetness += add_level;
113 6 100         if (wm->wetness > WETNESS_MAX) wm->wetness = WETNESS_MAX;
114 6 50         if (evap_step >= 0) wm->evap_step = evap_step; /* Update evap if specified */
115             } else {
116             wetness_magic_t *wm;
117             /* Break COW and upgrade to PVMG before attaching magic */
118 199 100         if (SvPOK(sv) && SvIsCOW(sv))
    100          
119 51           sv_force_normal_flags(sv, 0);
120 199 100         SvUPGRADE(sv, SVt_PVMG);
121 199           Newxz(wm, 1, wetness_magic_t);
122 199           wm->wetness = (add_level > WETNESS_MAX) ? WETNESS_MAX : add_level;
123 199 100         wm->evap_step = (evap_step >= 0) ? evap_step : EVAP_STEP_DEFAULT;
124 199           sv_magicext(sv, NULL, PERL_MAGIC_ext, &wetness_vtbl, (char *)wm, -1);
125             }
126 205           }
127              
128             /* Decrement wetness by evap_step and return current level (0–100) */
129             static int
130 195           tlaloc_read_wetness(pTHX_ SV *sv) {
131 195           MAGIC *mg = tlaloc_find_magic(aTHX_ sv);
132             wetness_magic_t *wm;
133 195 100         if (!mg || !mg->mg_ptr) return 0;
    50          
134 170           wm = (wetness_magic_t *)mg->mg_ptr;
135 170           wm->wetness -= wm->evap_step;
136 170 100         if (wm->wetness < 0) wm->wetness = 0;
137 170           return wm->wetness;
138             }
139              
140             /* ------------------------------------------------------------------ */
141             /* Tied wetness struct (for arrays and hashes) */
142             /* ------------------------------------------------------------------ */
143              
144             typedef struct {
145             SV *data; /* reference to underlying AV or HV */
146             int wetness; /* 0–100 */
147             int evap_step; /* evaporation rate */
148             int skip_evap; /* skip next evaporation (workaround for double-FETCH after STORE) */
149             } tied_wetness_t;
150              
151             static void
152 83           tied_evaporate(tied_wetness_t *tw) {
153 83 100         if (tw->skip_evap) {
154 4           tw->skip_evap = 0;
155 4           return;
156             }
157 79           tw->wetness -= tw->evap_step;
158 79 100         if (tw->wetness < 0) tw->wetness = 0;
159             }
160              
161             /* ------------------------------------------------------------------ */
162             /* Custom op implementations (pp_* functions) — 5.14+ only */
163             /* ------------------------------------------------------------------ */
164              
165             #if PERL_VERSION >= 14
166              
167             /* pp_tlaloc_wet: wet(sv [, evap_step]) */
168             static OP *
169 35           pp_tlaloc_wet(pTHX) {
170 35           dSP;
171             SV *sv;
172 35           int evap_step = -1;
173 35           I32 ax = TOPMARK + 1;
174             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
175 35           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
176            
177 35 50         if (items < 1)
178 0           croak("wet requires at least 1 argument");
179            
180 35           sv = PL_stack_base[ax];
181 35 100         if (items > 1)
182 2           evap_step = SvIV(PL_stack_base[ax + 1]);
183            
184 35 100         if (SvROK(sv)) sv = SvRV(sv);
185 35           tlaloc_attach_magic(aTHX_ sv, 50, evap_step);
186            
187 35           SP = PL_stack_base + TOPMARK;
188 35           PUTBACK;
189 35           return NORMAL;
190             }
191              
192             /* pp_tlaloc_drench: drench(sv [, evap_step]) */
193             static OP *
194 170           pp_tlaloc_drench(pTHX) {
195 170           dSP;
196             SV *sv;
197 170           int evap_step = -1;
198 170           I32 ax = TOPMARK + 1;
199             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
200 170           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
201            
202 170 50         if (items < 1)
203 0           croak("drench requires at least 1 argument");
204            
205 170           sv = PL_stack_base[ax];
206 170 100         if (items > 1)
207 44           evap_step = SvIV(PL_stack_base[ax + 1]);
208            
209 170 100         if (SvROK(sv)) sv = SvRV(sv);
210 170           tlaloc_remove_magic(aTHX_ sv);
211 170           tlaloc_attach_magic(aTHX_ sv, WETNESS_MAX, evap_step);
212            
213 170           SP = PL_stack_base + TOPMARK;
214 170           PUTBACK;
215 170           return NORMAL;
216             }
217              
218             /* pp_tlaloc_dry: dry(sv) */
219             static OP *
220 9           pp_tlaloc_dry(pTHX) {
221 9           dSP;
222             SV *sv;
223 9           I32 ax = TOPMARK + 1;
224             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
225 9           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
226            
227 9 50         if (items < 1)
228 0           croak("dry requires 1 argument");
229            
230 9           sv = PL_stack_base[ax];
231 9 100         if (SvROK(sv)) sv = SvRV(sv);
232 9           tlaloc_remove_magic(aTHX_ sv);
233            
234 9           SP = PL_stack_base + TOPMARK;
235 9           PUTBACK;
236 9           return NORMAL;
237             }
238              
239             /* pp_tlaloc_wetness: wetness(sv) -> int */
240             static OP *
241 146           pp_tlaloc_wetness(pTHX) {
242 146           dSP;
243             SV *sv;
244             int wetness;
245 146           I32 ax = TOPMARK + 1;
246             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
247 146           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
248            
249 146 50         if (items < 1)
250 0           croak("wetness requires 1 argument");
251            
252 146           sv = PL_stack_base[ax];
253 146 100         if (SvROK(sv)) sv = SvRV(sv);
254 146           wetness = tlaloc_read_wetness(aTHX_ sv);
255            
256 146           SP = PL_stack_base + TOPMARK;
257 146 50         XPUSHs(sv_2mortal(newSViv(wetness)));
258 146           PUTBACK;
259 146           return NORMAL;
260             }
261              
262             /* pp_tlaloc_is_wet: is_wet(sv) -> bool */
263             static OP *
264 18           pp_tlaloc_is_wet(pTHX) {
265 18           dSP;
266             SV *sv;
267             int wetness;
268 18           I32 ax = TOPMARK + 1;
269             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
270 18           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
271            
272 18 50         if (items < 1)
273 0           croak("is_wet requires 1 argument");
274            
275 18           sv = PL_stack_base[ax];
276 18 100         if (SvROK(sv)) sv = SvRV(sv);
277 18           wetness = tlaloc_read_wetness(aTHX_ sv);
278            
279 18           SP = PL_stack_base + TOPMARK;
280 18 50         XPUSHs(wetness > 0 ? &PL_sv_yes : &PL_sv_no);
    100          
281 18           PUTBACK;
282 18           return NORMAL;
283             }
284              
285             /* pp_tlaloc_is_dry: is_dry(sv) -> bool */
286             static OP *
287 31           pp_tlaloc_is_dry(pTHX) {
288 31           dSP;
289             SV *sv;
290             int wetness;
291 31           I32 ax = TOPMARK + 1;
292             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
293 31           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
294            
295 31 50         if (items < 1)
296 0           croak("is_dry requires 1 argument");
297            
298 31           sv = PL_stack_base[ax];
299 31 100         if (SvROK(sv)) sv = SvRV(sv);
300 31           wetness = tlaloc_read_wetness(aTHX_ sv);
301            
302 31           SP = PL_stack_base + TOPMARK;
303 31 50         XPUSHs(wetness == 0 ? &PL_sv_yes : &PL_sv_no);
    100          
304 31           PUTBACK;
305 31           return NORMAL;
306             }
307              
308             /* pp_tlaloc_evap_rate: evap_rate(sv [, new_rate]) -> int */
309             static OP *
310 20           pp_tlaloc_evap_rate(pTHX) {
311 20           dSP;
312             SV *sv;
313             MAGIC *mg;
314             wetness_magic_t *wm;
315 20           int result = 0;
316 20           I32 ax = TOPMARK + 1;
317             /* items = total args on stack between TOPMARK and SP, minus 1 for the CV */
318 20           I32 items = (SP - PL_stack_base - TOPMARK) - 1;
319            
320 20 50         if (items < 1)
321 0           croak("evap_rate requires at least 1 argument");
322            
323 20           sv = PL_stack_base[ax];
324 20 100         if (SvROK(sv)) sv = SvRV(sv);
325            
326 20           mg = tlaloc_find_magic(aTHX_ sv);
327 20 100         if (mg && mg->mg_ptr) {
    50          
328 19           wm = (wetness_magic_t *)mg->mg_ptr;
329 19 100         if (items > 1) {
330 5           wm->evap_step = SvIV(PL_stack_base[ax + 1]);
331             }
332 19           result = wm->evap_step;
333             }
334            
335 20           SP = PL_stack_base + TOPMARK;
336 20 50         XPUSHs(sv_2mortal(newSViv(result)));
337 20           PUTBACK;
338 20           return NORMAL;
339             }
340              
341             #endif /* PERL_VERSION >= 14 — end of pp_* functions */
342              
343             /* ------------------------------------------------------------------ */
344             /* Check functions to intercept XSUB calls and replace with custom ops */
345             /* ------------------------------------------------------------------ */
346              
347             #if PERL_VERSION >= 14
348             static CV *tlaloc_cv_wet;
349             static CV *tlaloc_cv_drench;
350             static CV *tlaloc_cv_dry;
351             static CV *tlaloc_cv_wetness;
352             static CV *tlaloc_cv_is_wet;
353             static CV *tlaloc_cv_is_dry;
354             static CV *tlaloc_cv_evap_rate;
355              
356             static OP *
357 35           tlaloc_ck_wet(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
358             PERL_UNUSED_ARG(namegv);
359             PERL_UNUSED_ARG(protosv);
360 35           entersubop->op_ppaddr = pp_tlaloc_wet;
361 35           return entersubop;
362             }
363              
364             static OP *
365 71           tlaloc_ck_drench(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
366             PERL_UNUSED_ARG(namegv);
367             PERL_UNUSED_ARG(protosv);
368 71           entersubop->op_ppaddr = pp_tlaloc_drench;
369 71           return entersubop;
370             }
371              
372             static OP *
373 9           tlaloc_ck_dry(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
374             PERL_UNUSED_ARG(namegv);
375             PERL_UNUSED_ARG(protosv);
376 9           entersubop->op_ppaddr = pp_tlaloc_dry;
377 9           return entersubop;
378             }
379              
380             static OP *
381 126           tlaloc_ck_wetness(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
382             PERL_UNUSED_ARG(namegv);
383             PERL_UNUSED_ARG(protosv);
384 126           entersubop->op_ppaddr = pp_tlaloc_wetness;
385 126           return entersubop;
386             }
387              
388             static OP *
389 18           tlaloc_ck_is_wet(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
390             PERL_UNUSED_ARG(namegv);
391             PERL_UNUSED_ARG(protosv);
392 18           entersubop->op_ppaddr = pp_tlaloc_is_wet;
393 18           return entersubop;
394             }
395              
396             static OP *
397 31           tlaloc_ck_is_dry(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
398             PERL_UNUSED_ARG(namegv);
399             PERL_UNUSED_ARG(protosv);
400 31           entersubop->op_ppaddr = pp_tlaloc_is_dry;
401 31           return entersubop;
402             }
403              
404             static OP *
405 20           tlaloc_ck_evap_rate(pTHX_ OP *entersubop, GV *namegv, SV *protosv) {
406             PERL_UNUSED_ARG(namegv);
407             PERL_UNUSED_ARG(protosv);
408 20           entersubop->op_ppaddr = pp_tlaloc_evap_rate;
409 20           return entersubop;
410             }
411             #endif
412              
413             /* ------------------------------------------------------------------ */
414             /* Exportable function names */
415             /* ------------------------------------------------------------------ */
416              
417             static const char * const tlaloc_exports[] = {
418             "wet", "drench", "dry", "wetness", "is_wet", "is_dry",
419             "evap_rate", "wet_tie", "untie_wet", NULL
420             };
421              
422             static void
423 81           tlaloc_export_to(pTHX_ HV *caller_stash, const char *name) {
424 81           HV *tlaloc_stash = gv_stashpvs("Tlaloc", 0);
425             GV **src_gvp;
426 81 50         if (!tlaloc_stash) return;
427 81           src_gvp = (GV **)hv_fetch(tlaloc_stash, name, strlen(name), FALSE);
428 81 50         if (src_gvp && *src_gvp && GvCV(*src_gvp)) {
    50          
    50          
429 81           CV *cv = GvCV(*src_gvp);
430 81           GV **dst_gvp = (GV **)hv_fetch(caller_stash, name, strlen(name), TRUE);
431 81           GV *dst = *dst_gvp;
432 81 50         if (SvTYPE(dst) != SVt_PVGV)
433 81           gv_init(dst, caller_stash, name, strlen(name), TRUE);
434 81           GvCV_set(dst, (CV *)SvREFCNT_inc(cv));
435 81           GvIMPORTED_CV_on(dst);
436             }
437             }
438              
439             MODULE = Tlaloc PACKAGE = Tlaloc
440              
441             PROTOTYPES: DISABLE
442              
443             BOOT:
444             {
445             #if PERL_VERSION >= 14
446             /* ------------------------------------------------------------------ */
447             /* Register custom ops with XOP descriptors */
448             /* ------------------------------------------------------------------ */
449              
450 10           XopENTRY_set(&tlaloc_xop_wet, xop_name, "tlaloc_wet");
451 10           XopENTRY_set(&tlaloc_xop_wet, xop_desc, "wet a scalar");
452 10           Perl_custom_op_register(aTHX_ pp_tlaloc_wet, &tlaloc_xop_wet);
453              
454 10           XopENTRY_set(&tlaloc_xop_drench, xop_name, "tlaloc_drench");
455 10           XopENTRY_set(&tlaloc_xop_drench, xop_desc, "drench a scalar");
456 10           Perl_custom_op_register(aTHX_ pp_tlaloc_drench, &tlaloc_xop_drench);
457              
458 10           XopENTRY_set(&tlaloc_xop_dry, xop_name, "tlaloc_dry");
459 10           XopENTRY_set(&tlaloc_xop_dry, xop_desc, "dry a scalar");
460 10           Perl_custom_op_register(aTHX_ pp_tlaloc_dry, &tlaloc_xop_dry);
461              
462 10           XopENTRY_set(&tlaloc_xop_wetness, xop_name, "tlaloc_wetness");
463 10           XopENTRY_set(&tlaloc_xop_wetness, xop_desc, "get wetness level");
464 10           Perl_custom_op_register(aTHX_ pp_tlaloc_wetness, &tlaloc_xop_wetness);
465              
466 10           XopENTRY_set(&tlaloc_xop_is_wet, xop_name, "tlaloc_is_wet");
467 10           XopENTRY_set(&tlaloc_xop_is_wet, xop_desc, "check if wet");
468 10           Perl_custom_op_register(aTHX_ pp_tlaloc_is_wet, &tlaloc_xop_is_wet);
469              
470 10           XopENTRY_set(&tlaloc_xop_is_dry, xop_name, "tlaloc_is_dry");
471 10           XopENTRY_set(&tlaloc_xop_is_dry, xop_desc, "check if dry");
472 10           Perl_custom_op_register(aTHX_ pp_tlaloc_is_dry, &tlaloc_xop_is_dry);
473              
474 10           XopENTRY_set(&tlaloc_xop_evap_rate, xop_name, "tlaloc_evap_rate");
475 10           XopENTRY_set(&tlaloc_xop_evap_rate, xop_desc, "get/set evaporation rate");
476 10           Perl_custom_op_register(aTHX_ pp_tlaloc_evap_rate, &tlaloc_xop_evap_rate);
477              
478             /* ------------------------------------------------------------------ */
479             /* Hook XSUBs to use custom ops via cv_set_call_checker */
480             /* ------------------------------------------------------------------ */
481              
482 10           tlaloc_cv_wet = get_cv("Tlaloc::wet", 0);
483 10           cv_set_call_checker(tlaloc_cv_wet, tlaloc_ck_wet, (SV *)tlaloc_cv_wet);
484              
485 10           tlaloc_cv_drench = get_cv("Tlaloc::drench", 0);
486 10           cv_set_call_checker(tlaloc_cv_drench, tlaloc_ck_drench, (SV *)tlaloc_cv_drench);
487              
488 10           tlaloc_cv_dry = get_cv("Tlaloc::dry", 0);
489 10           cv_set_call_checker(tlaloc_cv_dry, tlaloc_ck_dry, (SV *)tlaloc_cv_dry);
490              
491 10           tlaloc_cv_wetness = get_cv("Tlaloc::wetness", 0);
492 10           cv_set_call_checker(tlaloc_cv_wetness, tlaloc_ck_wetness, (SV *)tlaloc_cv_wetness);
493              
494 10           tlaloc_cv_is_wet = get_cv("Tlaloc::is_wet", 0);
495 10           cv_set_call_checker(tlaloc_cv_is_wet, tlaloc_ck_is_wet, (SV *)tlaloc_cv_is_wet);
496              
497 10           tlaloc_cv_is_dry = get_cv("Tlaloc::is_dry", 0);
498 10           cv_set_call_checker(tlaloc_cv_is_dry, tlaloc_ck_is_dry, (SV *)tlaloc_cv_is_dry);
499              
500 10           tlaloc_cv_evap_rate = get_cv("Tlaloc::evap_rate", 0);
501 10           cv_set_call_checker(tlaloc_cv_evap_rate, tlaloc_ck_evap_rate, (SV *)tlaloc_cv_evap_rate);
502             #endif
503             }
504              
505             void
506             import(SV *class, ...)
507             PREINIT:
508             HV *caller_stash;
509             int i, j;
510             const char *arg;
511             STRLEN len;
512             PPCODE:
513             /* During 'use' at compile time, PL_curcop points at the use statement
514             in the calling package, so CopSTASH gives us the correct caller */
515 10           caller_stash = CopSTASH(PL_curcop);
516            
517 10 100         if (items == 1) {
518             /* No args: export nothing */
519 1           XSRETURN_EMPTY;
520             }
521            
522 18 100         for (i = 1; i < items; i++) {
523 9           arg = SvPV(ST(i), len);
524 9 50         if (strEQ(arg, "all")) {
525 90 100         for (j = 0; tlaloc_exports[j]; j++) {
526 81           tlaloc_export_to(aTHX_ caller_stash, tlaloc_exports[j]);
527             }
528             } else {
529             /* Individual function name */
530 0 0         for (j = 0; tlaloc_exports[j]; j++) {
531 0 0         if (strEQ(arg, tlaloc_exports[j])) {
532 0           tlaloc_export_to(aTHX_ caller_stash, arg);
533 0           break;
534             }
535             }
536 0 0         if (!tlaloc_exports[j]) {
537 0           croak("'%s' is not exported by Tlaloc", arg);
538             }
539             }
540             }
541 9           XSRETURN_EMPTY;
542              
543             void
544             wet(sv, ...)
545             SV *sv
546             PREINIT:
547 0           int evap_step = -1; /* -1 means not specified */
548             CODE:
549 0 0         if (SvROK(sv)) sv = SvRV(sv);
550 0 0         if (items > 1) evap_step = SvIV(ST(1));
551 0           tlaloc_attach_magic(aTHX_ sv, 50, evap_step);
552              
553             void
554             drench(sv, ...)
555             SV *sv
556             PREINIT:
557 0           int evap_step = -1; /* -1 means not specified */
558             CODE:
559 0 0         if (SvROK(sv)) sv = SvRV(sv);
560 0 0         if (items > 1) evap_step = SvIV(ST(1));
561 0           tlaloc_remove_magic(aTHX_ sv);
562 0           tlaloc_attach_magic(aTHX_ sv, WETNESS_MAX, evap_step);
563              
564             void
565             dry(sv)
566             SV *sv
567             CODE:
568 0 0         if (SvROK(sv)) sv = SvRV(sv);
569 0           tlaloc_remove_magic(aTHX_ sv);
570              
571             int
572             wetness(sv)
573             SV *sv
574             CODE:
575 0 0         if (SvROK(sv)) sv = SvRV(sv);
576 0           RETVAL = tlaloc_read_wetness(aTHX_ sv);
577             OUTPUT:
578             RETVAL
579              
580             int
581             is_wet(sv)
582             SV *sv
583             CODE:
584 0 0         if (SvROK(sv)) sv = SvRV(sv);
585 0 0         RETVAL = (tlaloc_read_wetness(aTHX_ sv) > 0) ? 1 : 0;
586             OUTPUT:
587             RETVAL
588              
589             int
590             is_dry(sv)
591             SV *sv
592             CODE:
593 0 0         if (SvROK(sv)) sv = SvRV(sv);
594 0 0         RETVAL = (tlaloc_read_wetness(aTHX_ sv) == 0) ? 1 : 0;
595             OUTPUT:
596             RETVAL
597              
598             int
599             evap_rate(sv, ...)
600             SV *sv
601             PREINIT:
602             MAGIC *mg;
603             wetness_magic_t *wm;
604             CODE:
605 0 0         if (SvROK(sv)) sv = SvRV(sv);
606 0           mg = tlaloc_find_magic(aTHX_ sv);
607 0 0         if (!mg || !mg->mg_ptr) {
    0          
608 0           RETVAL = 0; /* No magic, return 0 */
609             } else {
610 0           wm = (wetness_magic_t *)mg->mg_ptr;
611 0 0         if (items > 1) {
612 0           wm->evap_step = SvIV(ST(1));
613             }
614 0           RETVAL = wm->evap_step;
615             }
616             OUTPUT:
617             RETVAL
618              
619             SV *
620             wet_tie(ref, ...)
621             SV *ref
622             PREINIT:
623             int evap_step;
624             SV *tied_obj;
625             tied_wetness_t *tw;
626             SV *sv;
627             CODE:
628 25 100         evap_step = (items > 1) ? SvIV(ST(1)) : EVAP_STEP_DEFAULT;
629            
630 25 50         if (!SvROK(ref))
631 0           croak("wet_tie requires an array or hash reference");
632            
633 25           sv = SvRV(ref);
634            
635             /* Allocate tied struct */
636 25           Newxz(tw, 1, tied_wetness_t);
637 25           tw->wetness = WETNESS_MAX;
638 25           tw->evap_step = evap_step;
639            
640 25 100         if (SvTYPE(sv) == SVt_PVAV) {
641 14           AV *orig = (AV *)sv;
642             AV *copy;
643             SSize_t i, len;
644            
645             /* Copy array contents */
646 14           len = av_len(orig) + 1;
647 14           copy = newAV();
648 14           av_extend(copy, len - 1);
649 51 100         for (i = 0; i < len; i++) {
650 37           SV **elem = av_fetch(orig, i, 0);
651 37 50         if (elem) av_store(copy, i, SvREFCNT_inc(*elem));
652             }
653 14           tw->data = newRV_noinc((SV *)copy);
654            
655             /* Create blessed object */
656 14           tied_obj = newSV(0);
657 14           sv_setiv(newSVrv(tied_obj, "Tlaloc::Tied::Array"), PTR2IV(tw));
658            
659             /* Clear original array BEFORE adding tie (to avoid triggering tied CLEAR) */
660 14           av_clear(orig);
661            
662             /* Tie the array - store the blessed reference in magic */
663 14           sv_magic((SV *)orig, tied_obj, PERL_MAGIC_tied, NULL, 0);
664            
665 14           RETVAL = tied_obj;
666             }
667 11 100         else if (SvTYPE(sv) == SVt_PVHV) {
668 9           HV *orig = (HV *)sv;
669             HV *copy;
670             HE *entry;
671            
672             /* Copy hash contents — reuse pre-computed hash to avoid re-hashing */
673 9           copy = newHV();
674 9           hv_iterinit(orig);
675 24 100         while ((entry = hv_iternext(orig))) {
676 15           hv_store(copy, HeKEY(entry), HeKLEN(entry),
677             SvREFCNT_inc(HeVAL(entry)), HeHASH(entry));
678             }
679 9           tw->data = newRV_noinc((SV *)copy);
680            
681             /* Create blessed object */
682 9           tied_obj = newSV(0);
683 9           sv_setiv(newSVrv(tied_obj, "Tlaloc::Tied::Hash"), PTR2IV(tw));
684            
685             /* Clear original hash BEFORE adding tie (to avoid triggering tied CLEAR) */
686 9           hv_clear(orig);
687            
688             /* Tie the hash - store the blessed reference in magic */
689 9           sv_magic((SV *)orig, tied_obj, PERL_MAGIC_tied, NULL, 0);
690            
691 9           RETVAL = tied_obj;
692             }
693             else {
694 2           Safefree(tw);
695 2           croak("wet_tie requires an array or hash reference");
696             }
697             OUTPUT:
698             RETVAL
699              
700             void
701             untie_wet(ref)
702             SV *ref
703             PREINIT:
704             SV *sv;
705             MAGIC *mg;
706             tied_wetness_t *tw;
707             CODE:
708 2 50         if (!SvROK(ref)) XSRETURN_EMPTY;
709 2           sv = SvRV(ref);
710            
711 2           mg = mg_find(sv, PERL_MAGIC_tied);
712 2 50         if (!mg || !mg->mg_obj) XSRETURN_EMPTY;
    50          
713            
714 2 100         if (SvTYPE(sv) == SVt_PVAV) {
715 1           SV *tied_sv = mg->mg_obj;
716 1 50         if (sv_derived_from(tied_sv, "Tlaloc::Tied::Array")) {
717 1           AV *orig = (AV *)sv;
718             AV *data_av;
719             SSize_t i, len;
720             AV *copy;
721            
722 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(tied_sv)));
723 1 50         if (tw && tw->data && SvROK(tw->data)) {
    50          
    50          
724 1           data_av = (AV *)SvRV(tw->data);
725            
726             /* Copy data BEFORE removing magic (DESTROY will free tw) */
727 1           len = av_len(data_av) + 1;
728 1           copy = newAV();
729 1           av_extend(copy, len - 1);
730 4 100         for (i = 0; i < len; i++) {
731 3           SV **elem = av_fetch(data_av, i, 0);
732 3 50         if (elem) av_store(copy, i, SvREFCNT_inc(*elem));
733             }
734            
735             /* Remove tie magic (this may trigger DESTROY) */
736 1           sv_unmagic(sv, PERL_MAGIC_tied);
737            
738             /* Restore data from our copy */
739 1           av_clear(orig);
740 1           len = av_len(copy) + 1;
741 4 100         for (i = 0; i < len; i++) {
742 3           SV **elem = av_fetch(copy, i, 0);
743 3 50         if (elem) av_store(orig, i, SvREFCNT_inc(*elem));
744             }
745 1           SvREFCNT_dec((SV *)copy);
746             }
747             }
748             }
749 1 50         else if (SvTYPE(sv) == SVt_PVHV) {
750 1           SV *tied_sv = mg->mg_obj;
751 1 50         if (sv_derived_from(tied_sv, "Tlaloc::Tied::Hash")) {
752 1           HV *orig = (HV *)sv;
753             HV *data_hv;
754             HE *entry;
755              
756 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(tied_sv)));
757 1 50         if (tw && tw->data && SvROK(tw->data)) {
    50          
    50          
758 1           data_hv = (HV *)SvRV(tw->data);
759              
760             /* Bump the HV's refcount so DESTROY (which decrements tw->data
761             the RV) doesn't free the underlying HV when sv_unmagic fires */
762 1           SvREFCNT_inc((SV *)data_hv);
763              
764             /* Remove tie magic (this triggers DESTROY, freeing tw + the RV) */
765 1           sv_unmagic(sv, PERL_MAGIC_tied);
766              
767             /* Restore directly from data_hv — no intermediate copy needed.
768             Reuse pre-computed hashes to avoid re-hashing each key. */
769 1           hv_clear(orig);
770 1           hv_iterinit(data_hv);
771 4 100         while ((entry = hv_iternext(data_hv))) {
772 3           hv_store(orig, HeKEY(entry), HeKLEN(entry),
773             SvREFCNT_inc(HeVAL(entry)), HeHASH(entry));
774             }
775 1           SvREFCNT_dec((SV *)data_hv);
776             }
777             }
778             }
779              
780             # ================================================================
781             # TIED ARRAY PACKAGE
782             # ================================================================
783              
784             MODULE = Tlaloc PACKAGE = Tlaloc::Tied::Array
785              
786             void
787             DESTROY(self)
788             SV *self
789             PREINIT:
790             tied_wetness_t *tw;
791             CODE:
792 14           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
793 14 50         if (tw) {
794 14 50         if (tw->data) SvREFCNT_dec(tw->data);
795 14           Safefree(tw);
796             }
797              
798             SV *
799             FETCH(self, idx)
800             SV *self
801             IV idx
802             PREINIT:
803             tied_wetness_t *tw;
804             AV *data;
805             SV **elem;
806             CODE:
807 25           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
808 25           tied_evaporate(tw);
809 25           data = (AV *)SvRV(tw->data);
810 25           elem = av_fetch(data, idx, 0);
811 25 50         RETVAL = elem ? SvREFCNT_inc(*elem) : &PL_sv_undef;
812             OUTPUT:
813             RETVAL
814              
815             void
816             STORE(self, idx, val)
817             SV *self
818             IV idx
819             SV *val
820             PREINIT:
821             tied_wetness_t *tw;
822             AV *data;
823             CODE:
824 2           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
825 2           tw->skip_evap = 1; /* Workaround: next FETCH is spurious internal call */
826 2           data = (AV *)SvRV(tw->data);
827 2           av_store(data, idx, SvREFCNT_inc(val));
828              
829             IV
830             FETCHSIZE(self)
831             SV *self
832             PREINIT:
833             tied_wetness_t *tw;
834             AV *data;
835             CODE:
836 6           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
837 6           tied_evaporate(tw);
838 6           data = (AV *)SvRV(tw->data);
839 6 50         RETVAL = av_len(data) + 1;
840             OUTPUT:
841             RETVAL
842              
843             void
844             STORESIZE(self, count)
845             SV *self
846             IV count
847             PREINIT:
848             tied_wetness_t *tw;
849             AV *data;
850             CODE:
851 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
852 0           data = (AV *)SvRV(tw->data);
853 0           av_fill(data, count - 1);
854              
855             int
856             EXISTS(self, idx)
857             SV *self
858             IV idx
859             PREINIT:
860             tied_wetness_t *tw;
861             AV *data;
862             CODE:
863 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
864 1           tied_evaporate(tw);
865 1           data = (AV *)SvRV(tw->data);
866 1 50         RETVAL = av_exists(data, idx);
867             OUTPUT:
868             RETVAL
869              
870             SV *
871             DELETE(self, idx)
872             SV *self
873             IV idx
874             PREINIT:
875             tied_wetness_t *tw;
876             AV *data;
877             CODE:
878 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
879 0           data = (AV *)SvRV(tw->data);
880 0           RETVAL = av_delete(data, idx, 0);
881 0 0         if (!RETVAL) RETVAL = &PL_sv_undef;
882 0           else SvREFCNT_inc(RETVAL);
883             OUTPUT:
884             RETVAL
885              
886             void
887             CLEAR(self)
888             SV *self
889             PREINIT:
890             tied_wetness_t *tw;
891             AV *data;
892             CODE:
893 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
894 0           data = (AV *)SvRV(tw->data);
895 0           av_clear(data);
896              
897             IV
898             PUSH(self, ...)
899             SV *self
900             PREINIT:
901             tied_wetness_t *tw;
902             AV *data;
903             int i;
904             CODE:
905 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
906 1           data = (AV *)SvRV(tw->data);
907 2 100         for (i = 1; i < items; i++) {
908 1           av_push(data, SvREFCNT_inc(ST(i)));
909             }
910 1 50         RETVAL = av_len(data) + 1;
911             OUTPUT:
912             RETVAL
913              
914             SV *
915             POP(self)
916             SV *self
917             PREINIT:
918             tied_wetness_t *tw;
919             AV *data;
920             CODE:
921 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
922 1           tied_evaporate(tw);
923 1           data = (AV *)SvRV(tw->data);
924 1           RETVAL = av_pop(data);
925 1 50         if (!RETVAL) RETVAL = &PL_sv_undef;
926             OUTPUT:
927             RETVAL
928              
929             SV *
930             SHIFT(self)
931             SV *self
932             PREINIT:
933             tied_wetness_t *tw;
934             AV *data;
935             CODE:
936 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
937 1           tied_evaporate(tw);
938 1           data = (AV *)SvRV(tw->data);
939 1           RETVAL = av_shift(data);
940 1 50         if (!RETVAL) RETVAL = &PL_sv_undef;
941             OUTPUT:
942             RETVAL
943              
944             IV
945             UNSHIFT(self, ...)
946             SV *self
947             PREINIT:
948             tied_wetness_t *tw;
949             AV *data;
950             int i;
951             CODE:
952 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
953 0           data = (AV *)SvRV(tw->data);
954 0           av_unshift(data, items - 1);
955 0 0         for (i = 1; i < items; i++) {
956 0           av_store(data, i - 1, SvREFCNT_inc(ST(i)));
957             }
958 0 0         RETVAL = av_len(data) + 1;
959             OUTPUT:
960             RETVAL
961              
962             void
963             SPLICE(self, ...)
964             SV *self
965             PREINIT:
966             tied_wetness_t *tw;
967             AV *data;
968             IV offset, length, i, sz;
969             AV *result;
970             PPCODE:
971 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
972 0           tied_evaporate(tw);
973 0           data = (AV *)SvRV(tw->data);
974 0           sz = av_len(data) + 1;
975            
976 0 0         offset = (items > 1) ? SvIV(ST(1)) : 0;
977 0 0         if (offset < 0) offset += sz;
978 0 0         if (offset < 0) offset = 0;
979 0 0         if (offset > sz) offset = sz;
980            
981 0 0         length = (items > 2) ? SvIV(ST(2)) : sz - offset;
982 0 0         if (length < 0) length = 0;
983 0 0         if (offset + length > sz) length = sz - offset;
984            
985             /* Collect removed elements */
986 0           result = newAV();
987 0 0         for (i = 0; i < length; i++) {
988 0           SV **elem = av_fetch(data, offset + i, 0);
989 0 0         if (elem) av_push(result, SvREFCNT_inc(*elem));
990             }
991            
992             /* Remove old elements */
993 0 0         for (i = 0; i < length; i++) {
994 0           av_delete(data, offset, G_DISCARD);
995             }
996            
997             /* Shift remaining elements */
998 0 0         if (length > 0 && offset < sz - length) {
    0          
999 0 0         for (i = offset; i < sz - length; i++) {
1000 0           SV **elem = av_fetch(data, i + length, 0);
1001 0 0         if (elem) av_store(data, i, SvREFCNT_inc(*elem));
1002             }
1003 0           av_fill(data, sz - length - 1);
1004             }
1005            
1006             /* Insert new elements (items - 3 new elements starting at ST(3)) */
1007 0 0         if (items > 3) {
1008 0           IV new_count = items - 3;
1009 0           IV new_sz = av_len(data) + 1;
1010 0           av_extend(data, new_sz + new_count - 1);
1011             /* Shift existing elements to make room */
1012 0 0         for (i = new_sz - 1; i >= offset; i--) {
1013 0           SV **elem = av_fetch(data, i, 0);
1014 0 0         if (elem) av_store(data, i + new_count, SvREFCNT_inc(*elem));
1015             }
1016             /* Insert new elements */
1017 0 0         for (i = 0; i < new_count; i++) {
1018 0           av_store(data, offset + i, SvREFCNT_inc(ST(3 + i)));
1019             }
1020             }
1021            
1022             /* Return removed elements */
1023 0           sz = av_len(result) + 1;
1024 0 0         EXTEND(SP, sz);
    0          
1025 0 0         for (i = 0; i < sz; i++) {
1026 0           SV **elem = av_fetch(result, i, 0);
1027 0 0         PUSHs(elem ? sv_2mortal(SvREFCNT_inc(*elem)) : &PL_sv_undef);
1028             }
1029 0           SvREFCNT_dec(result);
1030              
1031             int
1032             wetness(self)
1033             SV *self
1034             PREINIT:
1035             tied_wetness_t *tw;
1036             CODE:
1037 16           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1038 16           tied_evaporate(tw);
1039 16 50         RETVAL = tw->wetness;
1040             OUTPUT:
1041             RETVAL
1042              
1043             int
1044             is_wet(self)
1045             SV *self
1046             PREINIT:
1047             tied_wetness_t *tw;
1048             CODE:
1049 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1050 0           tied_evaporate(tw);
1051 0 0         RETVAL = (tw->wetness > 0) ? 1 : 0;
1052             OUTPUT:
1053             RETVAL
1054              
1055             int
1056             is_dry(self)
1057             SV *self
1058             PREINIT:
1059             tied_wetness_t *tw;
1060             CODE:
1061 4           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1062 4           tied_evaporate(tw);
1063 4 100         RETVAL = (tw->wetness == 0) ? 1 : 0;
1064             OUTPUT:
1065             RETVAL
1066              
1067             int
1068             evap_rate(self, ...)
1069             SV *self
1070             PREINIT:
1071             tied_wetness_t *tw;
1072             CODE:
1073 4           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1074 4 100         if (items > 1) {
1075 1           tw->evap_step = SvIV(ST(1));
1076             }
1077 4 50         RETVAL = tw->evap_step;
1078             OUTPUT:
1079             RETVAL
1080              
1081             void
1082             drench(self, ...)
1083             SV *self
1084             PREINIT:
1085             tied_wetness_t *tw;
1086             CODE:
1087 2           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1088 2           tw->wetness = WETNESS_MAX;
1089 2 100         if (items > 1) {
1090 1           tw->evap_step = SvIV(ST(1));
1091             }
1092              
1093             void
1094             wet(self, ...)
1095             SV *self
1096             PREINIT:
1097             tied_wetness_t *tw;
1098             CODE:
1099 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1100 0           tw->wetness += 50;
1101 0 0         if (tw->wetness > WETNESS_MAX) tw->wetness = WETNESS_MAX;
1102 0 0         if (items > 1) {
1103 0           tw->evap_step = SvIV(ST(1));
1104             }
1105              
1106             # ================================================================
1107             # TIED HASH PACKAGE
1108             # ================================================================
1109              
1110             MODULE = Tlaloc PACKAGE = Tlaloc::Tied::Hash
1111              
1112             void
1113             DESTROY(self)
1114             SV *self
1115             PREINIT:
1116             tied_wetness_t *tw;
1117             CODE:
1118 9           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1119 9 50         if (tw) {
1120 9 50         if (tw->data) SvREFCNT_dec(tw->data);
1121 9           Safefree(tw);
1122             }
1123              
1124             SV *
1125             FETCH(self, key)
1126             SV *self
1127             SV *key
1128             PREINIT:
1129             tied_wetness_t *tw;
1130             HV *data;
1131             SV **val;
1132             STRLEN klen;
1133             const char *kstr;
1134             CODE:
1135 13           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1136 13           tied_evaporate(tw);
1137 13           data = (HV *)SvRV(tw->data);
1138 13           kstr = SvPV(key, klen);
1139 13           val = hv_fetch(data, kstr, klen, 0);
1140 13 50         RETVAL = val ? SvREFCNT_inc(*val) : &PL_sv_undef;
1141             OUTPUT:
1142             RETVAL
1143              
1144             void
1145             STORE(self, key, val)
1146             SV *self
1147             SV *key
1148             SV *val
1149             PREINIT:
1150             tied_wetness_t *tw;
1151             HV *data;
1152             STRLEN klen;
1153             const char *kstr;
1154             CODE:
1155 2           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1156 2           tw->skip_evap = 1; /* Workaround: next FETCH is spurious internal call */
1157 2           data = (HV *)SvRV(tw->data);
1158 2           kstr = SvPV(key, klen);
1159 2           hv_store(data, kstr, klen, SvREFCNT_inc(val), 0);
1160              
1161             int
1162             EXISTS(self, key)
1163             SV *self
1164             SV *key
1165             PREINIT:
1166             tied_wetness_t *tw;
1167             HV *data;
1168             STRLEN klen;
1169             const char *kstr;
1170             CODE:
1171 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1172 1           tied_evaporate(tw);
1173 1           data = (HV *)SvRV(tw->data);
1174 1           kstr = SvPV(key, klen);
1175 1 50         RETVAL = hv_exists(data, kstr, klen);
1176             OUTPUT:
1177             RETVAL
1178              
1179             SV *
1180             DELETE(self, key)
1181             SV *self
1182             SV *key
1183             PREINIT:
1184             tied_wetness_t *tw;
1185             HV *data;
1186             STRLEN klen;
1187             const char *kstr;
1188             CODE:
1189 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1190 0           data = (HV *)SvRV(tw->data);
1191 0           kstr = SvPV(key, klen);
1192 0           RETVAL = hv_delete(data, kstr, klen, 0);
1193 0 0         if (!RETVAL) RETVAL = &PL_sv_undef;
1194 0           else SvREFCNT_inc(RETVAL);
1195             OUTPUT:
1196             RETVAL
1197              
1198             void
1199             CLEAR(self)
1200             SV *self
1201             PREINIT:
1202             tied_wetness_t *tw;
1203             HV *data;
1204             CODE:
1205 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1206 0           data = (HV *)SvRV(tw->data);
1207 0           hv_clear(data);
1208              
1209             SV *
1210             FIRSTKEY(self)
1211             SV *self
1212             PREINIT:
1213             tied_wetness_t *tw;
1214             HV *data;
1215             HE *entry;
1216             CODE:
1217 2           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1218 2           tied_evaporate(tw);
1219 2           data = (HV *)SvRV(tw->data);
1220 2           hv_iterinit(data);
1221 2           entry = hv_iternext(data);
1222 2 50         if (entry) {
1223 2           RETVAL = newSVpvn(HeKEY(entry), HeKLEN(entry));
1224             } else {
1225 0           RETVAL = &PL_sv_undef;
1226             }
1227             OUTPUT:
1228             RETVAL
1229              
1230             SV *
1231             NEXTKEY(self, lastkey)
1232             SV *self
1233             SV *lastkey
1234             PREINIT:
1235             tied_wetness_t *tw;
1236             HV *data;
1237             HE *entry;
1238             CODE:
1239 5           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1240 5           data = (HV *)SvRV(tw->data);
1241 5           entry = hv_iternext(data);
1242 5 100         if (entry) {
1243 3           RETVAL = newSVpvn(HeKEY(entry), HeKLEN(entry));
1244             } else {
1245 2           RETVAL = &PL_sv_undef;
1246             }
1247             OUTPUT:
1248             RETVAL
1249              
1250             SV *
1251             SCALAR(self)
1252             SV *self
1253             PREINIT:
1254             tied_wetness_t *tw;
1255             HV *data;
1256             CODE:
1257 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1258 0           tied_evaporate(tw);
1259 0           data = (HV *)SvRV(tw->data);
1260 0 0         RETVAL = newSViv(HvUSEDKEYS(data));
1261             OUTPUT:
1262             RETVAL
1263              
1264             int
1265             wetness(self)
1266             SV *self
1267             PREINIT:
1268             tied_wetness_t *tw;
1269             CODE:
1270 9           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1271 9           tied_evaporate(tw);
1272 9 50         RETVAL = tw->wetness;
1273             OUTPUT:
1274             RETVAL
1275              
1276             int
1277             is_wet(self)
1278             SV *self
1279             PREINIT:
1280             tied_wetness_t *tw;
1281             CODE:
1282 0           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1283 0           tied_evaporate(tw);
1284 0 0         RETVAL = (tw->wetness > 0) ? 1 : 0;
1285             OUTPUT:
1286             RETVAL
1287              
1288             int
1289             is_dry(self)
1290             SV *self
1291             PREINIT:
1292             tied_wetness_t *tw;
1293             CODE:
1294 4           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1295 4           tied_evaporate(tw);
1296 4 100         RETVAL = (tw->wetness == 0) ? 1 : 0;
1297             OUTPUT:
1298             RETVAL
1299              
1300             int
1301             evap_rate(self, ...)
1302             SV *self
1303             PREINIT:
1304             tied_wetness_t *tw;
1305             CODE:
1306 2           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1307 2 50         if (items > 1) {
1308 0           tw->evap_step = SvIV(ST(1));
1309             }
1310 2 50         RETVAL = tw->evap_step;
1311             OUTPUT:
1312             RETVAL
1313              
1314             void
1315             drench(self, ...)
1316             SV *self
1317             PREINIT:
1318             tied_wetness_t *tw;
1319             CODE:
1320 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1321 1           tw->wetness = WETNESS_MAX;
1322 1 50         if (items > 1) {
1323 1           tw->evap_step = SvIV(ST(1));
1324             }
1325              
1326             void
1327             wet(self, ...)
1328             SV *self
1329             PREINIT:
1330             tied_wetness_t *tw;
1331             CODE:
1332 1           tw = INT2PTR(tied_wetness_t *, SvIV(SvRV(self)));
1333 1           tw->wetness += 50;
1334 1 50         if (tw->wetness > WETNESS_MAX) tw->wetness = WETNESS_MAX;
1335 1 50         if (items > 1) {
1336 0           tw->evap_step = SvIV(ST(1));
1337             }