/* You may distribute under the terms of either the GNU General Public License
* or the Artistic License (the same terms as Perl itself)
*
* (C) Paul Evans, 2022 -- leonerd@leonerd.org.uk
*/
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "future.h"
#include "perl-backcompat.c.inc"
#include "av-utils.c.inc"
#define warn_void_context(func) S_warn_void_context(aTHX_ func)
static void S_warn_void_context(pTHX_ const char *func)
{
if(GIMME_V == G_VOID)
warn("Calling ->%s in void context", func);
}
#define CHECK_INSTANCE(self) \
if(!SvROK(self) || !SvOBJECT(SvRV(self)) || \
!sv_derived_from(self, "Future::XS")) { \
GV *gv = CvGV(cv); HV *stash = GvSTASH(gv); \
croak("Expected a Future instance for %s::%s", \
HvNAME(stash), GvNAME(gv)); \
}
MODULE = Future::XS PACKAGE = Future::XS
SV *
new(SV *proto)
CODE:
if(SvROK(proto) && SvOBJECT(SvRV(proto))) {
HV *protostash = SvSTASH(SvRV(proto));
RETVAL = future_new(HvNAME(protostash));
}
else
RETVAL = future_new(SvPV_nolen(proto));
OUTPUT:
RETVAL
void
DESTROY(SV *self)
CODE:
future_destroy(self);
bool
is_ready(SV *self)
CODE:
CHECK_INSTANCE(self);
RETVAL = future_is_ready(self);
OUTPUT:
RETVAL
bool
is_done(SV *self)
CODE:
CHECK_INSTANCE(self);
RETVAL = future_is_done(self);
OUTPUT:
RETVAL
bool
is_failed(SV *self)
CODE:
CHECK_INSTANCE(self);
RETVAL = future_is_failed(self);
OUTPUT:
RETVAL
bool
is_cancelled(SV *self)
CODE:
CHECK_INSTANCE(self);
RETVAL = future_is_cancelled(self);
OUTPUT:
RETVAL
char *
state(SV *self)
CODE:
CHECK_INSTANCE(self);
// TODO: We can do this more efficiently sometime
if(!future_is_ready(self))
RETVAL = "pending";
else if(future_is_failed(self))
RETVAL = "failed";
else if(future_is_cancelled(self))
RETVAL = "cancelled";
else
RETVAL = "done";
OUTPUT:
RETVAL
SV *
done(SV *self, ...)
CODE:
if(sv_is_future(self))
RETVAL = SvREFCNT_inc(ST(0));
else
RETVAL = future_new(SvPV_nolen(ST(0)));
future_donev(RETVAL, &ST(1), items - 1);
OUTPUT:
RETVAL
SV *
fail(SV *self, ...)
ALIAS:
fail = 0
die = 1
CODE:
SV *exception = ST(1);
if(ix == 1 && /* ->die */
!SvROK(exception) && SvPV_nolen(exception)[SvCUR(exception)-1] != '\n') {
ST(1) = exception = newSVsv(exception);
sv_catpvf(exception, " at %s line %d\n", CopFILE(PL_curcop), CopLINE(PL_curcop));
}
// TODO: mess about with Future::Exception
if(sv_is_future(self))
RETVAL = SvREFCNT_inc(ST(0));
else
RETVAL = future_new(SvPV_nolen(ST(0)));
future_failv(RETVAL, &ST(1), items - 1);
OUTPUT:
RETVAL
SV *
on_cancel(SV *self, SV *code)
CODE:
CHECK_INSTANCE(self);
RETVAL = newSVsv(self);
future_on_cancel(self, code);
OUTPUT:
RETVAL
SV *
on_ready(SV *self, SV *code)
CODE:
CHECK_INSTANCE(self);
/* Need to copy the return value first in case on_ready destroys it
* RT145168 */
RETVAL = newSVsv(self);
future_on_ready(self, code);
OUTPUT:
RETVAL
SV *
await(SV *self)
CODE:
CHECK_INSTANCE(self);
if(future_is_ready(self)) {
RETVAL = newSVsv(ST(0));
XSRETURN(1);
}
croak("%" SVf " is not yet complete and does not provide an ->await method",
SVfARG(self));
OUTPUT:
RETVAL
void
result(SV *self)
ALIAS:
result = FALSE
get = TRUE
PPCODE:
CHECK_INSTANCE(self);
/* This PUTBACK + SPAGAIN pair is required in case future_get_result_av()
* causes the arguments stack to be reällocated. It works fine on perls
* 5.24+ but causes older perls to crash. For now we just depend on 5.24
* https://rt.cpan.org/Ticket/Display.html?id=145597
*/
PUTBACK;
AV *result = future_get_result_av(self, ix);
SPAGAIN;
if(GIMME_V == G_LIST) {
XPUSHs_from_AV(result);
XSRETURN(av_count(result));
}
else {
if(av_count(result))
XPUSHs(AvARRAY(result)[0]);
else
XPUSHs(&PL_sv_undef);
XSRETURN(1);
}
SV *
on_done(SV *self, SV *code)
CODE:
CHECK_INSTANCE(self);
RETVAL = newSVsv(self);
future_on_done(self, code);
OUTPUT:
RETVAL
void
failure(SV *self)
PPCODE:
CHECK_INSTANCE(self);
PUTBACK;
AV *failure = future_get_failure_av(self);
SPAGAIN;
if(!failure)
XSRETURN(0);
if(GIMME_V == G_LIST) {
XPUSHs_from_AV(failure);
XSRETURN(av_count(failure));
}
else {
if(av_count(failure))
XPUSHs(AvARRAY(failure)[0]);
else
XPUSHs(&PL_sv_undef);
XSRETURN(1);
}
SV *
on_fail(SV *self, SV *code)
CODE:
CHECK_INSTANCE(self);
RETVAL = newSVsv(self);
future_on_fail(self, code);
OUTPUT:
RETVAL
SV *
cancel(SV *self)
CODE:
CHECK_INSTANCE(self);
future_cancel(self);
RETVAL = SvREFCNT_inc(self);
OUTPUT:
RETVAL
SV *
without_cancel(SV *self)
CODE:
RETVAL = future_without_cancel(self);
OUTPUT:
RETVAL
SV *
then(SV *self, ...)
ALIAS:
then = 0
then_with_f = FUTURE_THEN_WITH_F
CODE:
CHECK_INSTANCE(self);
if(GIMME_V == G_VOID) {
// Need to ensure we print the ->transform message right
const PERL_CONTEXT *cx = caller_cx(0, NULL);
if(CxTYPE(cx) == CXt_SUB &&
strEQ(GvNAME(CvGV(cx->blk_sub.cv)), "transform")) {
warn_void_context("transform");
}
else {
warn_void_context(ix ? "then_with_f" : "then");
}
}
items--; /* account for self */
SV *thencode = &PL_sv_undef;
if(items) {
thencode = newSVsv(ST(1));
items--;
}
SV *elsecode = &PL_sv_undef;
if(items % 2) {
elsecode = newSVsv(ST(1 + items));
items--;
}
if(items) {
HV *catches = newHV();
for(int i = 0; i < items/2; i++)
hv_store_ent(catches, ST(2 + i*2), newSVsv(ST(2 + i*2 + 1)), 0);
RETVAL = future_thencatch(self, ix, thencode, catches, elsecode);
}
else {
RETVAL = future_then(self, ix, thencode, elsecode);
}
OUTPUT:
RETVAL
SV *
else(SV *self, SV *code)
ALIAS:
else = 0
else_with_f = FUTURE_THEN_WITH_F
CODE:
CHECK_INSTANCE(self);
warn_void_context(ix ? "else_with_f" : "else");
RETVAL = future_then(self, ix, NULL, newSVsv(code));
OUTPUT:
RETVAL
SV *
catch(SV *self, ...)
ALIAS:
catch = 0
catch_with_f = FUTURE_THEN_WITH_F
CODE:
CHECK_INSTANCE(self);
warn_void_context(ix ? "catch_with_f" : "catch");
items--; /* account for self */
SV *elsecode = &PL_sv_undef;
if(items % 2) {
elsecode = newSVsv(ST(items));
items--;
}
HV *catches = newHV();
for(int i = 0; i < items/2; i++)
hv_store_ent(catches, ST(1 + i*2), newSVsv(ST(1 + i*2 + 1)), 0);
RETVAL = future_thencatch(self, ix, NULL, catches, elsecode);
OUTPUT:
RETVAL
SV *
followed_by(SV *self, SV *code)
CODE:
CHECK_INSTANCE(self);
warn_void_context("followed_by");
RETVAL = future_followed_by(self, newSVsv(code));
OUTPUT:
RETVAL
SV *
wait_all(SV *cls, ...)
CODE:
RETVAL = future_new_waitallv(SvPV_nolen(cls), &ST(1), items - 1);
OUTPUT:
RETVAL
SV *
wait_any(SV *cls, ...)
CODE:
RETVAL = future_new_waitanyv(SvPV_nolen(cls), &ST(1), items - 1);
OUTPUT:
RETVAL
SV *
needs_all(SV *cls, ...)
CODE:
RETVAL = future_new_needsallv(SvPV_nolen(cls), &ST(1), items - 1);
OUTPUT:
RETVAL
SV *
needs_any(SV *cls, ...)
CODE:
RETVAL = future_new_needsanyv(SvPV_nolen(cls), &ST(1), items - 1);
OUTPUT:
RETVAL
void
pending_futures(SV *self)
ALIAS:
pending_futures = FUTURE_SUBS_PENDING
ready_futures = FUTURE_SUBS_READY
done_futures = FUTURE_SUBS_DONE
failed_futures = FUTURE_SUBS_FAILED
cancelled_futures = FUTURE_SUBS_CANCELLED
PPCODE:
CHECK_INSTANCE(self);
PUTBACK;
Size_t count = future_mPUSH_subs(self, ix);
SPAGAIN;
XSRETURN(count);
SV *
btime(SV *self)
ALIAS:
btime = 0
rtime = 1
CODE:
{
struct timeval t;
switch(ix) {
case 0: t = future_get_btime(self); break;
case 1: t = future_get_rtime(self); break;
}
RETVAL = &PL_sv_undef;
if(t.tv_sec) {
AV *retav = newAV();
av_push(retav, newSViv(t.tv_sec));
av_push(retav, newSViv(t.tv_usec));
RETVAL = newRV_noinc((SV *)retav);
}
}
OUTPUT:
RETVAL
SV *
set_label(SV *self, SV *label)
CODE:
future_set_label(self, label);
RETVAL = SvREFCNT_inc(self);
OUTPUT:
RETVAL
SV *
label(SV *self)
CODE:
SV *label = future_get_label(self);
RETVAL = label ? newSVsv(label) : &PL_sv_undef;
OUTPUT:
RETVAL
SV *
set_udata(SV *self, SV *name, SV *value)
CODE:
future_set_udata(self, name, value);
RETVAL = SvREFCNT_inc(self);
OUTPUT:
RETVAL
SV *
udata(SV *self, SV *name)
CODE:
RETVAL = newSVsv(future_get_udata(self, name));
OUTPUT:
RETVAL
void
reread_environment()
CODE:
Future_reread_environment(aTHX);
BOOT:
future_boot();