1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
|
/*
py/pyext - python script object for PD and Max/MSP
Copyright (c)2002-2015 Thomas Grill (gr@grrrr.org)
For information on usage and redistribution, and for a DISCLAIMER OF ALL
WARRANTIES, see the file, "license.txt," in this distribution.
*/
#include "pyext.h"
#include <flinternal.h>
FLEXT_LIB_V("pyext pyext. pyx pyx.",pyext)
static const t_symbol *sym_get;
void pyext::Setup(t_classid c)
{
sym_get = flext::MakeSymbol("get");
FLEXT_CADDMETHOD_(c,0,"doc",m_doc);
FLEXT_CADDMETHOD_(c,0,"dir",m_dir);
#ifdef FLEXT_THREADS
FLEXT_CADDATTR_VAR1(c,"detach",detach);
FLEXT_CADDMETHOD_(c,0,"stop",m_stop);
#endif
FLEXT_CADDMETHOD_(c,0,"help",m_help);
FLEXT_CADDMETHOD_(c,0,"reload",m_reload_);
FLEXT_CADDMETHOD_(c,0,"reload.",m_reload);
FLEXT_CADDMETHOD_(c,0,"doc+",m_doc_);
FLEXT_CADDMETHOD_(c,0,"dir+",m_dir_);
FLEXT_CADDATTR_GET(c,"dir+",mg_dir_);
FLEXT_CADDATTR_VAR(c,"args",initargs,ms_initargs);
FLEXT_CADDMETHOD_(c,0,"get",m_get);
FLEXT_CADDMETHOD_(c,0,"set",m_set);
FLEXT_CADDMETHOD_(c,0,"edit",CbClick);
FLEXT_CADDATTR_VAR1(c,"py",pymsg);
FLEXT_CADDATTR_VAR1(c,"respond",respond);
// ----------------------------------------------------
// register/initialize pyext base class along with module
class_dict = PyDict_New();
PyObject *className = PyString_FromString(PYEXT_CLASS);
PyMethodDef *def;
// add setattr/getattr to class
for(def = attr_tbl; def->ml_name; def++) {
PyObject *func = PyCFunction_New(def, NULL);
PyDict_SetItemString(class_dict, def->ml_name, func);
Py_DECREF(func);
}
class_obj = PyClass_New(NULL, class_dict, className);
Py_DECREF(className);
// add methods to class
for (def = meth_tbl; def->ml_name != NULL; def++) {
PyObject *func = PyCFunction_New(def, NULL);
PyObject *method = PyMethod_New(func, NULL, class_obj); // increases class_obj ref count by 1
PyDict_SetItemString(class_dict, def->ml_name, method);
Py_DECREF(func);
Py_DECREF(method);
}
#if PY_VERSION_HEX >= 0x02020000
// not absolutely necessary, existent in python 2.2 upwards
// make pyext functions available in class scope
PyDict_Merge(class_dict,module_dict,0);
#endif
// after merge so that it's not in class_dict as well...
PyDict_SetItemString(module_dict, PYEXT_CLASS,class_obj); // increases class_obj ref count by 1
PyDict_SetItemString(class_dict,"__doc__",PyString_FromString(pyext_doc));
}
pyext *pyext::GetThis(PyObject *self)
{
PyObject *th = PyObject_GetAttrString(self,"_this");
if(th) {
pyext *ret = static_cast<pyext *>(PyLong_AsVoidPtr(th));
Py_DECREF(th);
return ret;
}
else {
PyErr_Clear();
return NULL;
}
}
void pyext::SetThis()
{
// remember the this pointer
PyObject *th = PyLong_FromVoidPtr(this);
PyObject_SetAttrString(pyobj,"_this",th); // ref is taken
}
void pyext::ClearThis()
{
int ret = PyObject_DelAttrString(pyobj,"_this");
FLEXT_ASSERT(ret != -1);
}
PyObject *pyext::class_obj = NULL;
PyObject *pyext::class_dict = NULL;
pyext::pyext(int argc,const t_atom *argv,bool sig):
methname(NULL),
pyobj(NULL),
inlets(-1),outlets(-1),
siginlets(0),sigoutlets(0)
#ifndef PY_USE_GIL
,pythr(NULL)
#endif
{
#ifdef FLEXT_THREADS
FLEXT_ADDTIMER(stoptmr,tick);
#endif
if(argc >= 2 && CanbeInt(argv[0]) && CanbeInt(argv[1])) {
inlets = GetAInt(argv[0]);
outlets = GetAInt(argv[1]);
argv += 2,argc -= 2;
}
if(sig && argc >= 2 && CanbeInt(argv[0]) && CanbeInt(argv[1])) {
siginlets = GetAInt(argv[0]);
sigoutlets = GetAInt(argv[1]);
argv += 2,argc -= 2;
}
const t_symbol *clname = NULL;
// check if the object name is pyext. , pyx. or similar
bool dotted = strrchr(thisName(),'.') != NULL;
ThrLockSys lock;
// init script module
if(argc) {
AddCurrentPath(this);
const t_symbol *scr = GetASymbol(*argv);
argv++,argc--;
if(scr) {
char modnm[64];
strcpy(modnm,GetString(scr));
if(!dotted) {
char *pt = strrchr(modnm,'.'); // search for last dot
if(pt && *pt) {
clname = MakeSymbol(pt+1);
*pt = 0;
}
}
ImportModule(modnm);
}
else
PyErr_SetString(PyExc_ValueError,"Invalid module name");
// check for alias creation names
if(dotted) clname = scr;
}
Register(GetRegistry(REGNAME));
if(argc || clname) {
if(!clname) {
clname = GetASymbol(*argv);
argv++,argc--;
}
if(clname)
methname = clname;
else
PyErr_SetString(PyExc_ValueError,"Invalid class name");
}
if(argc) initargs(argc,argv);
Report();
}
bool pyext::Init()
{
ThrLockSys lock;
if(methname) {
MakeInstance();
if(pyobj) InitInOut(inlets,outlets);
}
else
inlets = outlets = 0;
if(inlets < 0) inlets = 0;
if(outlets < 0) outlets = 0;
AddInSignal(siginlets);
AddInAnything((siginlets?0:1)+inlets);
AddOutSignal(sigoutlets);
AddOutAnything(outlets);
Report();
return pyobj && flext_dsp::Init();
}
bool pyext::Finalize()
{
bool ok = true;
ThrLockSys lock;
PyObject *init = PyObject_GetAttrString(pyobj,"_init"); // get ref
if(init) {
if(PyMethod_Check(init)) {
PyObject *res = PyObject_CallObject(init,NULL);
if(!res) {
// exception is set
ok = false;
// we want to know why __init__ failed...
PyErr_Print();
}
else
Py_DECREF(res);
}
Py_DECREF(init);
}
else
// __init__ has not been found - don't care
PyErr_Clear();
return ok && flext_dsp::Finalize();
}
void pyext::Exit()
{
pybase::Exit(); // exit threads
ThrLockSys lock;
DoExit();
Unregister(GetRegistry(REGNAME));
UnimportModule();
Report();
flext_dsp::Exit();
}
bool pyext::DoInit()
{
// call init now, after _this has been set, which is
// important for eventual callbacks from __init__ to c
PyObject *pargs = MakePyArgs(NULL,initargs.Count(),initargs.Atoms());
if(pargs) {
bool ok = true;
SetThis();
PyObject *init = PyObject_GetAttrString(pyobj,"__init__"); // get ref
if(init) {
if(PyMethod_Check(init)) {
PyObject *res = PyObject_CallObject(init,pargs);
if(!res) {
// exception is set
ok = false;
// we want to know why __init__ failed...
PyErr_Print();
}
else
Py_DECREF(res);
}
Py_DECREF(init);
}
else
// __init__ has not been found - don't care
PyErr_Clear();
Py_DECREF(pargs);
return ok;
}
else
return false;
}
void pyext::DoExit()
{
ClearBinding();
bool gcrun = false;
if(pyobj) {
// try to run del to clean up the class instance
PyObject *objdel = PyObject_GetAttrString(pyobj,"_del");
if(objdel) {
PyObject *ret = PyObject_CallObject(objdel,NULL);
if(ret)
Py_DECREF(ret);
else
PyErr_Print();
Py_DECREF(objdel);
}
else
// _del has not been found - don't care
PyErr_Clear();
ClearThis();
gcrun = pyobj->ob_refcnt > 1;
Py_DECREF(pyobj); // opposite of SetClssMeth
}
if(gcrun && !collect()) {
post("%s - Unloading: Object is still referenced",thisName());
}
}
bool pyext::InitInOut(int &inl,int &outl)
{
if(inl >= 0) {
// set number of inlets
int ret = PyObject_SetAttrString(pyobj,"_inlets",PyInt_FromLong(inl));
FLEXT_ASSERT(!ret);
}
if(outl >= 0) {
// set number of outlets
int ret = PyObject_SetAttrString(pyobj,"_outlets",PyInt_FromLong(outl));
FLEXT_ASSERT(!ret);
}
// __init__ can override the number of inlets and outlets
if(!DoInit()) // call __init__ constructor
return false;
if(inl < 0) {
// get number of inlets
inl = inlets;
PyObject *res = PyObject_GetAttrString(pyobj,"_inlets"); // get ref
if(res) {
if(PyCallable_Check(res)) {
PyObject *fres = PyEval_CallObject(res,NULL);
Py_DECREF(res);
res = fres;
}
if(PyInt_Check(res))
inl = PyInt_AS_LONG(res);
Py_DECREF(res);
}
else
PyErr_Clear();
}
if(outl < 0) {
// get number of outlets
outl = outlets;
PyObject *res = PyObject_GetAttrString(pyobj,"_outlets"); // get ref
if(res) {
if(PyCallable_Check(res)) {
PyObject *fres = PyEval_CallObject(res,NULL);
Py_DECREF(res);
res = fres;
}
if(PyInt_Check(res))
outl = PyInt_AS_LONG(res);
Py_DECREF(res);
}
else
PyErr_Clear();
}
return true;
}
bool pyext::MakeInstance()
{
// pyobj should already have been decref'd / cleared before getting here!!
if(module && methname) {
PyObject *pref = PyObject_GetAttrString(module,const_cast<char *>(GetString(methname)));
if(!pref)
PyErr_Print();
else {
if(PyClass_Check(pref)) {
// make instance, but don't call __init__
pyobj = PyInstance_NewRaw(pref,NULL);
if(!pyobj) PyErr_Print();
}
else
post("%s - Type of \"%s\" is unhandled!",thisName(),GetString(methname));
Py_DECREF(pref);
}
return true;
}
else
return false;
}
void pyext::LoadModule()
{
}
void pyext::UnloadModule()
{
}
void pyext::Load()
{
FLEXT_ASSERT(!pyobj);
bool ok = MakeInstance();
if(ok) {
int inl = -1,outl = -1;
ok = InitInOut(inl,outl);
if((inl >= 0 && inl != inlets) || (outl >= 0 && outl != outlets))
post("%s - Inlet and outlet count can't be changed by reload",thisName());
}
// return ok;
}
void pyext::Unload()
{
DoExit();
pyobj = NULL;
}
void pyext::m_get(const t_symbol *s)
{
ThrLockSys lock;
PyObject *pvar = PyObject_GetAttrString(pyobj,const_cast<char *>(GetString(s))); /* fetch bound method */
if(pvar) {
flext::AtomListStatic<16> lst;
const t_symbol *sym = GetPyArgs(lst,pvar,1);
if(sym) {
FLEXT_ASSERT(!IsAnything(sym));
// dump value to attribute outlet
SetSymbol(lst[0],s);
ToOutAnything(GetOutAttr(),sym_get,lst.Count(),lst.Atoms());
}
Py_DECREF(pvar);
}
Report();
}
void pyext::m_set(int argc,const t_atom *argv)
{
ThrLockSys lock;
if(argc < 2 || !IsString(argv[0]))
post("%s - Syntax: set varname arguments...",thisName());
else if(*GetString(argv[0]) == '_')
post("%s - set: variables with leading _ are reserved and can't be set",thisName());
else {
char *ch = const_cast<char *>(GetString(argv[0]));
if(PyObject_HasAttrString(pyobj,ch)) {
PyObject *pval = MakePyArgs(NULL,argc-1,argv+1);
if(pval) {
if(PySequence_Size(pval) == 1) {
// reduce lists of one element to element itself
PyObject *val1 = PySequence_GetItem(pval,0); // new reference
Py_DECREF(pval);
pval = val1;
}
PyObject_SetAttrString(pyobj,ch,pval);
Py_DECREF(pval);
}
}
}
Report();
}
bool pyext::CbMethodResort(int n,const t_symbol *s,int argc,const t_atom *argv)
{
if(!n)
return flext_dsp::CbMethodResort(n,s,argc,argv);
return pyobj && work(n,s,argc,argv);
}
void pyext::m_help()
{
post("");
post("%s %s - python class object, (C)2002-2012 Thomas Grill",thisName(),PY__VERSION);
#ifdef FLEXT_DEBUG
post("DEBUG VERSION, compiled on " __DATE__ " " __TIME__);
#endif
post("Arguments: %s {inlets outlets} [script name] [class name] {args...}",thisName());
post("Inlet 1: messages to control the pyext object");
post(" 2...: python inlets");
post("Outlets: python outlets");
post("Methods:");
post("\thelp: shows this help");
post("\treload {args...}: reload python script");
post("\treload. : reload with former arguments");
post("\tdoc: display module doc string");
post("\tdoc+: display class doc string");
post("\tdir: dump module dictionary");
post("\tdir+: dump class dictionary");
#ifdef FLEXT_THREADS
post("\tdetach 0/1: detach threads");
post("\tstop {wait time (ms)}: stop threads");
#endif
post("");
}
void pyext::callpy(PyObject *fun,PyObject *args)
{
PyObject *ret = PyObject_CallObject(fun,args);
if(ret) {
// function worked fine
if(!PyObject_Not(ret)) post("pyext - returned value is ignored");
Py_DECREF(ret);
}
}
bool pyext::call(const char *meth,int inlet,const t_symbol *s,int argc,const t_atom *argv)
{
bool ret = false;
ThrLock lock;
PyObject *pmeth = PyObject_GetAttrString(pyobj,const_cast<char *>(meth)); /* fetch bound method */
if(pmeth == NULL) {
PyErr_Clear(); // no method found
}
else {
PyObject *pargs = MakePyArgs(s,argc,argv,inlet?inlet:-1); //,true);
if(!pargs) {
PyErr_Print();
Py_DECREF(pmeth);
}
else {
gencall(pmeth,pargs);
ret = true;
}
}
return ret;
}
bool pyext::work(int n,const t_symbol *s,int argc,const t_atom *argv)
{
bool ret = false;
// should be enough...
char str[256];
// offset inlet index by signal inlets
// \note first one is shared with messages!
if(siginlets) n += siginlets-1;
// try tag/inlet
if(!ret) {
sprintf(str,"%s_%i",GetString(s),n);
ret = call(str,0,NULL,argc,argv);
}
if(!ret && argc == 1) {
if(s == sym_float) {
// try truncated float
t_atom at; SetInt(at,GetAInt(argv[0]));
sprintf(str,"int_%i",n);
ret = call(str,0,NULL,1,&at);
}
else if(s == sym_int) {
// try floating int
t_atom at; SetFloat(at,GetAFloat(argv[0]));
sprintf(str,"float_%i",n);
ret = call(str,0,NULL,1,&at);
}
}
// try anything/inlet
if(!ret) {
sprintf(str,"_anything_%i",n);
ret = call(str,0,s,argc,argv);
}
// try tag at any inlet
if(!ret) {
sprintf(str,"%s_",GetString(s));
ret = call(str,n,NULL,argc,argv);
}
if(!ret && argc == 1) {
if(s == sym_float) {
// try truncated float at any inlet
t_atom at; SetInt(at,GetAInt(argv[0]));
ret = call("int_",0,NULL,1,&at);
}
else if(s == sym_int) {
// try floating int at any inlet
t_atom at; SetFloat(at,GetAFloat(argv[0]));
ret = call("float_",0,NULL,1,&at);
}
}
if(!ret) {
// try anything at any inlet
const char *str1 = "_anything_";
if(s == sym_bang && !argc) {
t_atom argv;
SetSymbol(argv,sym__);
ret = call(str1,n,s,1,&argv);
}
else
ret = call(str1,n,s,argc,argv);
}
if(!ret)
// no matching python method found
post("%s - no matching method found for '%s' into inlet %i",thisName(),GetString(s),n);
Respond(ret);
return ret;
}
PyObject *pyext::GetSig(int ix,bool in) { return NULL; }
void pyext::CbClick() { pybase::OpenEditor(); }
bool pyext::CbDsp() { return false; }
void pyext::DumpOut(const t_symbol *sym,int argc,const t_atom *argv)
{
ToOutAnything(GetOutAttr(),sym?sym:thisTag(),argc,argv);
}
|