File Coverage

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