/* ** binding-util.h ** ** This file is part of mkxp. ** ** Copyright (C) 2013 Jonas Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #ifndef BINDING_UTIL_H #define BINDING_UTIL_H #include #ifndef MKXPZ_LEGACY_RUBY #include #else #include #endif #include "exception.h" #ifdef RUBY_API_VERSION_MAJOR #define RAPI_MAJOR RUBY_API_VERSION_MAJOR #define RAPI_MINOR RUBY_API_VERSION_MINOR #define RAPI_TEENY RUBY_API_VERSION_TEENY #else #define RAPI_MAJOR RUBY_VERSION_MAJOR #define RAPI_MINOR RUBY_VERSION_MINOR #define RAPI_TEENY RUBY_VERSION_TEENY #endif #define RAPI_FULL ((RAPI_MAJOR * 100) + (RAPI_MINOR * 10) + RAPI_TEENY) enum RbException { RGSS = 0, Reset, PHYSFS, SDL, MKXP, ErrnoENOENT, IOError, TypeError, ArgumentError, RbExceptionsMax }; struct RbData { VALUE exc[RbExceptionsMax]; /* Input module (RGSS3) */ VALUE buttoncodeHash; RbData(); ~RbData(); }; RbData *getRbData(); struct Exception; void raiseRbExc(const Exception &exc); #if RAPI_FULL > 187 #define DECL_TYPE(Klass) extern rb_data_type_t Klass##Type /* 2.1 has added a new field (flags) to rb_data_type_t */ #if RAPI_FULL >= 210 /* TODO: can mkxp use RUBY_TYPED_FREE_IMMEDIATELY here? */ #define DEF_TYPE_FLAGS 0 #else #define DEF_TYPE_FLAGS #endif #endif #if RAPI_MAJOR > 1 || RAPI_MINOR <= 9 #if RAPI_FULL < 270 #define DEF_TYPE_CUSTOMNAME_AND_FREE(Klass, Name, Free) \ rb_data_type_t Klass##Type = { \ Name, {0, Free, 0, {0, 0}}, 0, 0, DEF_TYPE_FLAGS} #else #define DEF_TYPE_CUSTOMNAME_AND_FREE(Klass, Name, Free) \ rb_data_type_t Klass##Type = {Name, {0, Free, 0, 0, 0}, 0, 0, DEF_TYPE_FLAGS} #endif #define DEF_TYPE_CUSTOMFREE(Klass, Free) \ DEF_TYPE_CUSTOMNAME_AND_FREE(Klass, #Klass, Free) #define DEF_TYPE_CUSTOMNAME(Klass, Name) \ DEF_TYPE_CUSTOMNAME_AND_FREE(Klass, Name, freeInstance) #define DEF_TYPE(Klass) DEF_TYPE_CUSTOMNAME(Klass, #Klass) #endif // Ruby 1.8 helper stuff #if RAPI_MAJOR < 2 #if RAPI_MINOR < 9 #define RUBY_T_FIXNUM T_FIXNUM #define RUBY_T_TRUE T_TRUE #define RUBY_T_FALSE T_FALSE #define RUBY_T_NIL T_NIL #define RUBY_T_UNDEF T_UNDEF #define RUBY_T_SYMBOL T_SYMBOL #define RUBY_T_FLOAT T_FLOAT #define RUBY_T_STRING T_STRING #define RUBY_T_ARRAY T_ARRAY #else #define T_FIXNUM RUBY_T_FIXNUM #define T_TRUE RUBY_T_TRUE #define T_FALSE RUBY_T_FALSE #define T_NIL RUBY_T_NIL #define T_UNDEF RUBY_T_UNDEF #define T_SYMBOL RUBY_T_SYMBOL #define T_FLOAT RUBY_T_FLOAT #define T_STRING RUBY_T_STRING #define T_ARRAY RUBY_T_ARRAY #endif #if RAPI_MINOR < 9 #define RUBY_Qtrue Qtrue #define RUBY_Qfalse Qfalse #define RUBY_Qnil Qnil #define RUBY_Qundef Qundef #endif #if RAPI_MINOR < 9 #define RB_FIXNUM_P(obj) FIXNUM_P(obj) #define RB_SYMBOL_P(obj) SYMBOL_P(obj) #define RB_TYPE_P(obj, type) \ (((type) == RUBY_T_FIXNUM) \ ? RB_FIXNUM_P(obj) \ : ((type) == RUBY_T_TRUE) \ ? ((obj) == RUBY_Qtrue) \ : ((type) == RUBY_T_FALSE) \ ? ((obj) == RUBY_Qfalse) \ : ((type) == RUBY_T_NIL) \ ? ((obj) == RUBY_Qnil) \ : ((type) == RUBY_T_UNDEF) \ ? ((obj) == RUBY_Qundef) \ : ((type) == RUBY_T_SYMBOL) \ ? RB_SYMBOL_P(obj) \ : (!SPECIAL_CONST_P(obj) && \ BUILTIN_TYPE(obj) == (type))) #endif #define OBJ_INIT_COPY(a, b) rb_obj_init_copy(a, b) #define DEF_ALLOCFUNC_CUSTOMFREE(type, free) \ static VALUE type##Allocate(VALUE klass) { \ return Data_Wrap_Struct(klass, 0, free, 0); \ } #define DEF_ALLOCFUNC(type) DEF_ALLOCFUNC_CUSTOMFREE(type, freeInstance) #if RAPI_MINOR < 9 #define rb_str_new_cstr rb_str_new2 #endif #define PRIsVALUE "s" #endif // end #if RAPI_FULL > 187 template static VALUE classAllocate(VALUE klass) { /* 2.3 has changed the name of this function */ #if RAPI_FULL >= 230 return rb_data_typed_object_wrap(klass, 0, rbType); #else return rb_data_typed_object_alloc(klass, 0, rbType); #endif } #endif template static void freeInstance(void *inst) { delete static_cast(inst); } void raiseDisposedAccess(VALUE self); template inline C *getPrivateData(VALUE self) { #if RAPI_FULL > 187 C *c = static_cast(RTYPEDDATA_DATA(self)); #else C *c = static_cast(DATA_PTR(self)); #endif if (!c) { raiseRbExc(Exception(Exception::MKXPError, "No instance data for variable (missing call to super?)")); } return c; } template static inline C * #if RAPI_FULL > 187 getPrivateDataCheck(VALUE self, const rb_data_type_t &type) #else getPrivateDataCheck(VALUE self, const char *type) #endif { #if RAPI_FULL <= 187 rb_check_type(self, T_DATA); VALUE otherObj = rb_const_get(rb_cObject, rb_intern(type)); const char *ownname, *othername; if (!rb_obj_is_kind_of(self, otherObj)) { ownname = rb_obj_classname(self); othername = rb_obj_classname(otherObj); rb_raise(rb_eTypeError, "Can't convert %s into %s", othername, ownname); } void *obj = DATA_PTR(self); #else const char *ownname = rb_obj_classname(self); if (!rb_typeddata_is_kind_of(self, &type)) rb_raise(rb_eTypeError, "Can't convert %s into %s", ownname, type.wrap_struct_name); void *obj = RTYPEDDATA_DATA(self); #endif return static_cast(obj); } static inline void setPrivateData(VALUE self, void *p) { #if RAPI_FULL > 187 RTYPEDDATA_DATA(self) = p; #else DATA_PTR(self) = p; #endif } inline VALUE #if RAPI_FULL > 187 wrapObject(void *p, const rb_data_type_t &type, VALUE underKlass = rb_cObject) #else wrapObject(void *p, const char *type, VALUE underKlass = rb_cObject) #endif { #if RAPI_FULL > 187 VALUE klass = rb_const_get(underKlass, rb_intern(type.wrap_struct_name)); #else VALUE klass = rb_const_get(underKlass, rb_intern(type)); #endif VALUE obj = rb_obj_alloc(klass); setPrivateData(obj, p); return obj; } inline VALUE wrapProperty(VALUE self, void *prop, const char *iv, #if RAPI_FULL > 187 const rb_data_type_t &type, #else const char *type, #endif VALUE underKlass = rb_cObject) { VALUE propObj = wrapObject(prop, type, underKlass); rb_iv_set(self, iv, propObj); return propObj; } /* Implemented: oSszfibn| */ int rb_get_args(int argc, VALUE *argv, const char *format, ...); /* Always terminate 'rb_get_args' with this */ #ifndef NDEBUG #define RB_ARG_END_VAL ((void *)-1) #define RB_ARG_END , RB_ARG_END_VAL #else #define RB_ARG_END #endif typedef VALUE (*RubyMethod)(int argc, VALUE *argv, VALUE self); static inline void _rb_define_method(VALUE klass, const char *name, RubyMethod func) { rb_define_method(klass, name, RUBY_METHOD_FUNC(func), -1); } static inline void rb_define_class_method(VALUE klass, const char *name, RubyMethod func) { rb_define_singleton_method(klass, name, RUBY_METHOD_FUNC(func), -1); } static inline void _rb_define_module_function(VALUE module, const char *name, RubyMethod func) { rb_define_module_function(module, name, RUBY_METHOD_FUNC(func), -1); } #define GUARD_EXC(exp) \ { \ try { \ exp \ } catch (const Exception &exc) { \ raiseRbExc(exc); \ } \ } template static inline VALUE objectLoad(int argc, VALUE *argv, VALUE self) { const char *data; int dataLen; rb_get_args(argc, argv, "s", &data, &dataLen RB_ARG_END); VALUE obj = rb_obj_alloc(self); C *c = 0; GUARD_EXC(c = C::deserialize(data, dataLen);); setPrivateData(obj, c); return obj; } static inline VALUE rb_bool_new(bool value) { return value ? Qtrue : Qfalse; } inline void rb_float_arg(VALUE arg, double *out, int argPos = 0) { switch (rb_type(arg)) { case RUBY_T_FLOAT: *out = RFLOAT_VALUE(arg); break; case RUBY_T_FIXNUM: *out = FIX2INT(arg); break; default: rb_raise(rb_eTypeError, "Argument %d: Expected float", argPos); } } inline void rb_int_arg(VALUE arg, int *out, int argPos = 0) { switch (rb_type(arg)) { case RUBY_T_FLOAT: // FIXME check int range? *out = NUM2LONG(arg); break; case RUBY_T_FIXNUM: *out = FIX2INT(arg); break; default: rb_raise(rb_eTypeError, "Argument %d: Expected fixnum", argPos); } } inline void rb_bool_arg(VALUE arg, bool *out, int argPos = 0) { switch (rb_type(arg)) { case RUBY_T_TRUE: *out = true; break; case RUBY_T_FALSE: case RUBY_T_NIL: *out = false; break; default: rb_raise(rb_eTypeError, "Argument %d: Expected bool", argPos); } } inline void rb_check_argc(int actual, int expected) { if (actual != expected) rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", actual, expected); } #if RAPI_MAJOR < 2 static inline void rb_error_arity(int argc, int min, int max) { if (argc > max || argc < min) rb_raise(rb_eArgError, "Finish me! rb_error_arity()"); // TODO } #if RAPI_MINOR < 9 static inline VALUE rb_sprintf(const char *fmt, ...) { return rb_str_new2("Finish me! rb_sprintf()"); // TODO } static inline VALUE rb_str_catf(VALUE obj, const char *fmt, ...) { return rb_str_new2("Finish me! rb_str_catf()"); // TODO } static inline VALUE rb_file_open_str(VALUE filename, const char *mode) { return rb_funcall(rb_cFile, rb_intern("open"), 2, filename, rb_str_new2(mode)); } #endif #endif #define RB_METHOD(name) static VALUE name(int argc, VALUE *argv, VALUE self) #define RB_UNUSED_PARAM \ { \ (void)argc; \ (void)argv; \ (void)self; \ } #define MARSH_LOAD_FUN(Typ) \ RB_METHOD(Typ##Load) { return objectLoad(argc, argv, self); } #define INITCOPY_FUN(Klass) \ RB_METHOD(Klass##InitializeCopy) { \ VALUE origObj; \ rb_get_args(argc, argv, "o", &origObj RB_ARG_END); \ if (!OBJ_INIT_COPY(self, origObj)) /* When would this fail??*/ \ return self; \ Klass *orig = getPrivateData(origObj); \ Klass *k = 0; \ GUARD_EXC(k = new Klass(*orig);) \ setPrivateData(self, k); \ return self; \ } /* Object property which is copied by reference, with allowed NIL * FIXME: Getter assumes prop is disposable, * because self.disposed? is not checked in this case. * Should make this more clear */ #if RAPI_FULL > 187 #define DEF_PROP_OBJ_REF(Klass, PropKlass, PropName, prop_iv) \ RB_METHOD(Klass##Get##PropName) { \ RB_UNUSED_PARAM; \ return rb_iv_get(self, prop_iv); \ } \ RB_METHOD(Klass##Set##PropName) { \ RB_UNUSED_PARAM; \ rb_check_argc(argc, 1); \ Klass *k = getPrivateData(self); \ VALUE propObj = *argv; \ PropKlass *prop; \ if (NIL_P(propObj)) \ prop = 0; \ else \ prop = getPrivateDataCheck(propObj, PropKlass##Type); \ GUARD_EXC(k->set##PropName(prop);) \ rb_iv_set(self, prop_iv, propObj); \ return propObj; \ } #else #define DEF_PROP_OBJ_REF(Klass, PropKlass, PropName, prop_iv) \ RB_METHOD(Klass##Get##PropName) { \ RB_UNUSED_PARAM; \ return rb_iv_get(self, prop_iv); \ } \ RB_METHOD(Klass##Set##PropName) { \ RB_UNUSED_PARAM; \ rb_check_argc(argc, 1); \ Klass *k = getPrivateData(self); \ VALUE propObj = *argv; \ PropKlass *prop; \ if (NIL_P(propObj)) \ prop = 0; \ else \ prop = getPrivateDataCheck(propObj, #PropKlass); \ GUARD_EXC(k->set##PropName(prop);) \ rb_iv_set(self, prop_iv, propObj); \ return propObj; \ } #endif /* Object property which is copied by value, not reference */ #if RAPI_FULL > 187 #define DEF_PROP_OBJ_VAL(Klass, PropKlass, PropName, prop_iv) \ RB_METHOD(Klass##Get##PropName) { \ RB_UNUSED_PARAM; \ checkDisposed(self); \ return rb_iv_get(self, prop_iv); \ } \ RB_METHOD(Klass##Set##PropName) { \ rb_check_argc(argc, 1); \ Klass *k = getPrivateData(self); \ VALUE propObj = *argv; \ PropKlass *prop; \ prop = getPrivateDataCheck(propObj, PropKlass##Type); \ GUARD_EXC(k->set##PropName(*prop);) \ return propObj; \ } #else #define DEF_PROP_OBJ_VAL(Klass, PropKlass, PropName, prop_iv) \ RB_METHOD(Klass##Get##PropName) { \ RB_UNUSED_PARAM; \ checkDisposed(self); \ return rb_iv_get(self, prop_iv); \ } \ RB_METHOD(Klass##Set##PropName) { \ rb_check_argc(argc, 1); \ Klass *k = getPrivateData(self); \ VALUE propObj = *argv; \ PropKlass *prop; \ prop = getPrivateDataCheck(propObj, #PropKlass); \ GUARD_EXC(k->set##PropName(*prop);) \ return propObj; \ } #endif #define DEF_PROP(Klass, type, PropName, arg_fun, value_fun) \ RB_METHOD(Klass##Get##PropName) { \ RB_UNUSED_PARAM; \ Klass *k = getPrivateData(self); \ type value = 0; \ GUARD_EXC(value = k->get##PropName();) \ return value_fun(value); \ } \ RB_METHOD(Klass##Set##PropName) { \ rb_check_argc(argc, 1); \ Klass *k = getPrivateData(self); \ type value; \ rb_##arg_fun##_arg(*argv, &value); \ GUARD_EXC(k->set##PropName(value);) \ return *argv; \ } #define DEF_PROP_I(Klass, PropName) \ DEF_PROP(Klass, int, PropName, int, rb_fix_new) #define DEF_PROP_F(Klass, PropName) \ DEF_PROP(Klass, double, PropName, float, rb_float_new) #define DEF_PROP_B(Klass, PropName) \ DEF_PROP(Klass, bool, PropName, bool, rb_bool_new) #define INIT_PROP_BIND(Klass, PropName, prop_name_s) \ { \ _rb_define_method(klass, prop_name_s, Klass##Get##PropName); \ _rb_define_method(klass, prop_name_s "=", Klass##Set##PropName); \ } #endif // BINDING_UTIL_H