1 /** 2 Dplug's wren bridge. 3 4 Copyright: Guillaume Piolat 2021. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module dplug.wren.wrensupport; 8 9 import core.stdc.string : memcpy, strlen, strcmp; 10 import core.stdc.stdlib : malloc, free; 11 import core.stdc.stdio : snprintf; 12 13 import std.traits: getSymbolsByUDA; 14 import std.meta: staticIndexOf; 15 16 import dplug.core.nogc; 17 import dplug.core.file; 18 import dplug.gui.context; 19 import dplug.gui.element; 20 import dplug.graphics.color; 21 22 import wren.vm; 23 import wren.value; 24 import wren.primitive; 25 import wren.common; 26 import dplug.wren.describe; 27 import dplug.wren.wren_ui; 28 29 nothrow @nogc: 30 31 /// 32 /// `WrenSupport` manages interaction between Wren and the plugin. 33 /// Such an object is created/destroyed/accessed with `enableWrenSupport()`, `disableWrenSupport()`, 34 /// and `wrenSupport()`. It is held, as all GUI globals in the `UIContext`. 35 /// 36 /// Example: 37 /// --- 38 /// // Inside your UI constructor 39 /// mixin(fieldIdentifiersAreIDs!DistortGUI); 40 /// context.enableWrenSupport(); 41 /// debug 42 /// context.wrenSupport.addModuleFileWatch("plugin", `/absolute/path/to/my/plugin.wren`); // Live-reload 43 /// else 44 /// context.wrenSupport.addModuleSource("plugin", import("plugin.wren")); // Final release has static scripts 45 /// context.wrenSupport.registerScriptExports!DistortGUI; 46 /// context.wrenSupport.callCreateUI(); 47 /// 48 /// // Inside your UI destructor 49 /// context.disableWrenSupport(); 50 /// --- 51 /// 52 /// See_also: `enableWrenSupport()`, `disableWrenSupport()` 53 /// 54 final class WrenSupport 55 { 56 @nogc: 57 58 /// Constructor. Use `context.enableWrenSupport()` instead. 59 this(IUIContext uiContext) nothrow 60 { 61 _uiContext = uiContext; // Note: wren VM start is deferred to first use. 62 } 63 64 /// Instantiate that with your main UI widget to register widgets classes. 65 /// Foreach member variable of `GUIClass` with the `@ScriptExport` attribute, this registers 66 /// a Wren class 67 /// The right Wren class can then be returned by the `$` operator and `UI.getElementById` methods. 68 /// 69 /// Note: the mirror Wren classes don't inherit from each other. Wren doesn't know our D hierarchy. 70 void registerScriptExports(GUIClass)() nothrow 71 { 72 // Automatically set widgets ID. _member.id = "_member"; 73 static foreach(m; getSymbolsByUDA!(GUIClass, ScriptExport)) 74 {{ 75 alias dClass = typeof(m); 76 registerUIElementClass!dClass(); 77 }} 78 } 79 80 /// Add a UIElement derivative class into the set of known classes in Wren, and all its parent up to UIElement 81 /// It is recommended that you use `@ScriptExport` on your fields and `registerScriptExports` instead, but this 82 /// can be used to register classes manually. 83 /// 84 /// Note: the mirror Wren classes don't inherit from each other. Wren doesn't know our D hierarchy. 85 void registerUIElementClass(ElemClass)() nothrow 86 { 87 // If you fail here: ony UIElement derivatives can have @ScriptExport 88 // Maybe you can expose this functionnality in a widget? 89 static assert(is(ElemClass: UIElement)); 90 91 string name = ElemClass.classinfo.name; 92 93 bool alreadyKnown = false; 94 95 // search if class already known 96 IdentifierBloom.HashResult hash = IdentifierBloom.hashOf(name); 97 if (_identifierBloom.couldContain(hash)) 98 { 99 foreach(e; _exportedClasses[]) 100 { 101 if (e.fullClassName() == name) 102 { 103 alreadyKnown = true; 104 break; 105 } 106 } 107 } 108 109 if (!alreadyKnown) 110 { 111 _identifierBloom.add(hash); 112 ScriptExportClass c = mallocNew!ScriptExportClass(ElemClass.classinfo); 113 registerDClass!ElemClass(c); 114 _exportedClasses ~= c; 115 } 116 } 117 118 /// Add a read-only Wren module source code, to be loaded once and never changed. 119 /// Release purpose. When in developement, you might prefer a reloadable file, with `addModuleFileWatch`. 120 void addModuleSource(const(char)[] moduleName, const(char)[] moduleSource) nothrow 121 { 122 // This forces a zero terminator. 123 char* moduleNameZ = stringDup(CString(moduleName).storage).ptr; 124 char* moduleSourceZ = stringDup(CString(moduleSource).storage).ptr; 125 126 PreloadedSource ps; 127 ps.moduleName = moduleNameZ; 128 ps.source = moduleSourceZ; 129 _preloadedSources.pushBack(ps); 130 } 131 132 /// Add a dynamic reloadable .wren source file. The module is reloaded and compared when `reloadScriptsThatChanged()` is called. 133 /// Development purpose. For a release plug-in, you want to use `addModuleSource` instead. 134 void addModuleFileWatch(const(char)[] moduleName, const(char)[] wrenFilePath) nothrow 135 { 136 // This forces a zero terminator. 137 char* moduleNameZ = stringDup(CString(moduleName).storage).ptr; 138 char* wrenFilePathZ = stringDup(CString(wrenFilePath).storage).ptr; 139 140 FileWatch fw; 141 fw.moduleName = moduleNameZ; 142 fw.wrenFilePath = wrenFilePathZ; 143 fw.lastSource = null; 144 145 _fileSources.pushBack(fw); 146 } 147 148 /// To call in your UI's constructor. This call the `Plugin.createUI` Wren method, in module "plugin". 149 /// 150 /// Note: Changing @ScriptProperty values does not make the elements dirty. 151 /// But since it's called at UI creation, the whole UI is dirty anyway so it works. 152 /// 153 /// Returns: `true` if no runtime or compile-time error. 154 bool callCreateUI() nothrow 155 { 156 return callPluginMethod("createUI"); 157 } 158 159 /// To call in your UI's `reflow()` method. This call the `Plugin.reflow` Wren method, in module "plugin". 160 /// 161 /// Note: Changing @ScriptProperty values does not make the elements dirty. 162 /// But since it's called at UI reflow, the whole UI is dirty anyway so it works. 163 /// 164 /// Returns: `true` if no runtime or compile-time error. 165 bool callReflow() nothrow 166 { 167 return callPluginMethod("reflow"); 168 } 169 170 /// Read live-reload .wren files and restart the Wren VM if they have changed. 171 /// Check the registered .wren file and check if they have changed. 172 /// Since it (re)starts the Wren VM, it cannot be called from Wren. 173 /// Returns: `true` on first load, or if the scripts have changed. 174 bool reloadScriptsThatChanged() nothrow 175 { 176 bool oneScriptChanged = false; 177 foreach(ref fw; _fileSources) 178 { 179 if (fw.updateAndReturnIfChanged()) 180 oneScriptChanged = true; 181 } 182 183 // If a script changed, we need to restart the whole Wren VM since there is no way to forger about a module! 184 if (oneScriptChanged) 185 stopWrenVM(); 186 187 // then ensure Wren VM is on 188 startWrenVM(); 189 return oneScriptChanged; 190 } 191 192 /// Call this in your `onAnimate` callback. This polls script regularly and force a full redraw. 193 /// 194 /// Example: 195 /// --- 196 /// override void onAnimate(double dt, double time) 197 /// { 198 /// context.wrenSupport.callReflowWhenScriptsChange(dt); 199 /// } 200 /// --- 201 /// 202 /// Returns: `true` if no runtime or compile-time error. 203 bool callReflowWhenScriptsChange(double dt) nothrow 204 { 205 enum CHECK_EVERY_N_SECS = 0.2; // 200 ms 206 _timeSinceLastScriptCheck += dt; 207 if (_timeSinceLastScriptCheck > CHECK_EVERY_N_SECS) 208 { 209 _timeSinceLastScriptCheck = 0; 210 if (reloadScriptsThatChanged()) 211 { 212 // We detected a change, we need to call Plugin.reflow() in Wren. 213 return callReflow(); 214 } 215 } 216 return true; 217 } 218 219 // <advanced API> 220 221 /// Call Plugin.<methodName>, a static method without arguments in Wren. For advanced users only. 222 /// Returns: `true` if no runtime or compile-time error. 223 bool callPluginMethod(const(char)* methodName) nothrow 224 { 225 reloadScriptsThatChanged(); 226 enum int MAX = 64+MAX_VARIABLE_NAME*2; 227 char[MAX] code; 228 snprintf(code.ptr, MAX, "{\n \nimport \"plugin\" for Plugin\n Plugin.%s()\n}\n", methodName); 229 return interpret(code.ptr); 230 } 231 232 /// Interpret arbitrary code. For advanced users only. 233 /// Returns: `true` if no runtime or compile-time error. 234 bool interpret(const(char)* path, const(char)* source) nothrow 235 { 236 try 237 { 238 WrenInterpretResult result = wrenInterpret(_vm, path, source); 239 return result == WrenInterpretResult.WREN_RESULT_SUCCESS; 240 } 241 catch(Exception e) 242 { 243 // Note: error reported by another mechanism anyway (debug output). 244 destroyFree(e); 245 return false; 246 } 247 } 248 249 /// Interpret arbitrary code. For advanced users only. 250 /// Returns: `true` if no runtime or compile-time error. 251 bool interpret(const(char)* source) nothrow 252 { 253 return interpret("", source); 254 } 255 256 // </advanced API> 257 258 ~this() nothrow 259 { 260 stopWrenVM(); 261 262 foreach(ps; _preloadedSources[]) 263 { 264 free(ps.moduleName); 265 free(ps.source); 266 } 267 268 foreach(fw; _fileSources[]) 269 { 270 free(fw.moduleName); 271 free(fw.wrenFilePath); 272 free(fw.lastSource); 273 } 274 275 foreach(ec; _exportedClasses[]) 276 { 277 destroyFree(ec); 278 } 279 } 280 281 package: 282 283 IUIContext uiContext() nothrow 284 { 285 return _uiContext; 286 } 287 288 ScriptPropertyDesc* getScriptProperty(int nthClass, int nthProp) nothrow 289 { 290 ScriptExportClass sec = _exportedClasses[nthClass]; 291 ScriptPropertyDesc[] descs = sec.properties(); 292 return &descs[nthProp]; 293 } 294 295 private: 296 297 WrenVM* _vm = null; 298 IUIContext _uiContext; 299 double _timeSinceLastScriptCheck = 0; // in seconds 300 IdentifierBloom _identifierBloom; 301 302 static struct PreloadedSource 303 { 304 char* moduleName; 305 char* source; 306 } 307 308 static struct FileWatch 309 { 310 nothrow: 311 @nogc: 312 char* moduleName; 313 char* wrenFilePath; 314 char* lastSource; 315 316 bool updateAndReturnIfChanged() 317 { 318 // FUTURE: eventually use stat to get date of change instead 319 char* newSource = readWrenFile(); 320 321 // Sometimes reading the file fails, and then we should just retry later. Report no changes. 322 if (newSource is null) 323 return false; 324 325 if ((lastSource is null) || strcmp(lastSource, newSource) != 0) 326 { 327 free(lastSource); 328 lastSource = newSource; 329 return true; 330 } 331 else 332 { 333 free(newSource); 334 return false; 335 } 336 } 337 338 char* readWrenFile() 339 { 340 ubyte[] content = readFile(wrenFilePath); 341 return cast(char*) content.ptr; // correct because readFile return one trailing '\0' 342 } 343 } 344 345 /// All known premade modules. 346 Vec!PreloadedSource _preloadedSources; 347 348 /// All known file-watch modules. 349 Vec!FileWatch _fileSources; 350 351 /// All known D @ScriptExport classes. 352 Vec!ScriptExportClass _exportedClasses; 353 354 /// "widgets" module source, recreated on import based upon _exportedClasses content. 355 Vec!char _widgetModuleSource; 356 357 /// The number of time a Wren VM has been started. This is to invalidate caching of Wren values. 358 uint _vmGeneration = 0; 359 360 /// Wren module, its look-up is cached to speed-up $ operator. 361 ObjModule* _cachedUIModule, 362 _cachedWidgetsModule; 363 364 // ui.Element class, its look-up is cached to speed-up $ operator. 365 ObjClass* _cachedClassElement; 366 367 void startWrenVM() nothrow 368 { 369 if (_vm !is null) 370 return; // Already started 371 372 try 373 { 374 WrenConfiguration config; 375 wrenInitConfiguration(&config); 376 377 config.writeFn = &dplug_wrenPrint; 378 config.errorFn = &dplug_wrenError; 379 config.dollarOperatorFn = &dplug_wrenDollarOperator; 380 config.bindForeignMethodFn = &dplug_wrenBindForeignMethod; 381 config.bindForeignClassFn = &dplug_wrenBindForeignClass; 382 config.loadModuleFn = &dplug_wrenLoadModule; 383 config.userData = cast(void*)this; 384 385 // Note: wren defaults for memory usage make a lot of sense. 386 _vm = wrenNewVM(&config); 387 _vmGeneration++; 388 _cachedUIModule = null; 389 _cachedWidgetsModule = null; 390 _cachedClassElement = null; 391 } 392 catch(Exception e) 393 { 394 debugLog("VM initialization failed"); 395 destroyFree(e); 396 _vm = null; 397 } 398 } 399 400 void stopWrenVM() nothrow 401 { 402 if (_vm !is null) 403 { 404 try 405 { 406 wrenFreeVM(_vm); 407 } 408 catch(Exception e) 409 { 410 destroyFree(e); 411 } 412 _vm = null; 413 } 414 } 415 416 void print(const(char)* text) nothrow 417 { 418 debugLog(text); 419 } 420 421 void error(WrenErrorType type, const(char)* module_, int line, const(char)* message) nothrow 422 { 423 switch(type) 424 { 425 case WrenErrorType.WREN_ERROR_COMPILE: 426 debugLogf("%s(%d): Error: %s\n", module_, line, message); 427 break; 428 429 case WrenErrorType.WREN_ERROR_RUNTIME: 430 debugLogf("wren crash: %s\n", message); 431 break; 432 433 case WrenErrorType.WREN_ERROR_STACK_TRACE: 434 debugLogf(" %s.%s:%d\n", module_, message, line); 435 break; 436 437 default: 438 assert(false); 439 } 440 } 441 442 // this is called anytime Wren looks for a foreign method 443 WrenForeignMethodFn foreignMethod(WrenVM* vm, const(char)* module_, const(char)* className, bool isStatic, const(char)* signature) nothrow 444 { 445 // Introducing the Dplug-Wren standard library here. 446 try 447 { 448 if (strcmp(module_, "ui") == 0) 449 { 450 return wrenUIBindForeignMethod(vm, className, isStatic, signature); 451 } 452 return null; 453 } 454 catch(Exception e) 455 { 456 destroyFree(e); 457 return null; 458 } 459 } 460 461 // this is called anytime Wren looks for a foreign class 462 WrenForeignClassMethods foreignClass(WrenVM* vm, const(char)* module_, const(char)* className) nothrow 463 { 464 if (strcmp(module_, "ui") == 0) 465 return wrenUIForeignClass(vm, className); 466 467 WrenForeignClassMethods methods; 468 methods.allocate = null; 469 methods.finalize = null; 470 return methods; 471 } 472 473 WrenLoadModuleResult loadModule(WrenVM* vm, const(char)* name) nothrow 474 { 475 WrenLoadModuleResult res; 476 res.source = null; 477 res.onComplete = null; 478 res.userData = null; 479 480 // Try file-watch source first 481 foreach(fw; _fileSources[]) 482 { 483 if (strcmp(name, fw.moduleName) == 0) 484 { 485 assert(fw.lastSource); // should have parsed the file preventively 486 res.source = fw.lastSource; 487 goto found; 488 } 489 } 490 491 // Try preloaded source first 492 foreach(ps; _preloadedSources[]) 493 { 494 if (strcmp(name, ps.moduleName) == 0) 495 { 496 res.source = ps.source; 497 goto found; 498 } 499 } 500 501 502 try 503 { 504 if (strcmp(name, "widgets") == 0) 505 res.source = widgetModuleSource(); 506 507 if (strcmp(name, "ui") == 0) 508 res.source = wrenUIModuleSource(); 509 } 510 catch(Exception e) 511 { 512 destroyFree(e); 513 res.source = null; 514 } 515 found: 516 return res; 517 } 518 519 // Note: return a value whose lifetime is tied to Wren VM. 520 ObjModule* getWrenModule(const(char)* name) 521 { 522 Value moduleName = wrenStringFormat(_vm, "$", name); 523 wrenPushRoot(_vm, AS_OBJ(moduleName)); 524 ObjModule* wModule = getModule(_vm, moduleName); 525 wrenPopRoot(_vm); 526 return wModule; 527 } 528 529 ObjClass* getWrenClassForThisUIELement(UIElement elem) 530 { 531 // Do we have a valid cached ObjClass* inside the UIElement? 532 void* cachedClass = elem.getUserPointer(UIELEMENT_POINTERID_WREN_EXPORTED_CLASS); 533 if (cachedClass !is null) 534 { 535 // Same Wren VM? 536 uint cacheGen = cast(uint) elem.getUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION); 537 if (cacheGen == _vmGeneration) 538 { 539 // yes, reuse 540 return cast(ObjClass*) cachedClass; 541 } 542 } 543 544 enum UIELEMENT_POINTERID_WREN_EXPORTED_CLASS = 0; /// The cached Wren class of this UIElement. 545 enum UIELEMENT_POINTERID_WREN_VM_GENERATION = 1; /// The Wren VM count, as it is restarted. Stored as void*, but is an uint. 546 547 // try to find concrete class directly 548 ObjClass* classTarget; 549 ScriptExportClass concreteClassInfo = findExportedClassByClassInfo(elem.classinfo); 550 if (concreteClassInfo) 551 { 552 const(char)* wrenClassName = concreteClassInfo.wrenClassNameZ(); 553 classTarget = AS_CLASS(wrenFindVariable(_vm, _cachedWidgetsModule, wrenClassName)); 554 } 555 else 556 { 557 // $ return a UIElement, no classes were found 558 // This is a silent error, properties assignment will silently fail. 559 // We still cache that incorrect value, as the real fix is registering the class to Wren. 560 classTarget = AS_CLASS(wrenFindVariable(_vm, _cachedUIModule, "UIElement")); 561 } 562 563 // Cached return value inside the UIElement, which will speed-up future $ 564 elem.setUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION, classTarget); 565 elem.setUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION, cast(void*) _vmGeneration); 566 return classTarget; 567 } 568 569 // Implementation of the $ operator. 570 bool dollarOperator(WrenVM* vm, Value* args) nothrow 571 { 572 try 573 { 574 if (!IS_STRING(args[0])) 575 return RETURN_NULL(args); 576 577 const(char)* id = AS_STRING(args[0]).value.ptr; 578 579 // Note: elem can be null here. If no element is found with a proper ID, 580 // return an unbound value. 581 UIElement elem = _uiContext.getElementById(id); 582 if (elem is null) 583 return RETURN_NULL(args); // $(id) not found 584 585 // Find a Wren class we have to convert it to. 586 // $ can be any of the imported classes in "widgets", if not it is an UIElement. 587 // Note that both "ui" and "widgets" module MUST be imported. 588 589 if (_cachedUIModule is null) 590 { 591 _cachedUIModule = getWrenModule("ui"); 592 if (_cachedUIModule is null) 593 return RETURN_ERROR(vm, "module \"ui\" is not imported"); 594 } 595 596 if (_cachedWidgetsModule is null) 597 { 598 _cachedWidgetsModule = getWrenModule("widgets"); 599 if (_cachedWidgetsModule is null) 600 return RETURN_ERROR(vm, "module \"widgets\" is not imported"); 601 } 602 603 ObjClass* classTarget = getWrenClassForThisUIELement(elem); 604 605 if (_cachedClassElement is null) 606 { 607 _cachedClassElement = AS_CLASS(wrenFindVariable(vm, _cachedUIModule, "Element")); 608 } 609 610 if (classTarget is null) 611 { 612 return RETURN_ERROR(vm, "cannot create a IUElement from operator $"); 613 } 614 615 Value obj = wrenNewInstance(vm, classTarget); 616 617 // PERF: could this be also cached? It wouldn't be managed by Wren. 618 // Create new Element foreign 619 ObjForeign* foreign = wrenNewForeign(vm, _cachedClassElement, UIElementBridge.sizeof); 620 UIElementBridge* bridge = cast(UIElementBridge*) foreign.data.ptr; 621 bridge.elem = elem; 622 623 // Assign it in the first field of the newly created ui.UIElement 624 ObjInstance* instance = AS_INSTANCE(obj); 625 instance.fields[0] = OBJ_VAL(foreign); 626 627 return RETURN_OBJ(args, AS_INSTANCE(obj)); 628 } 629 catch(Exception e) 630 { 631 destroyFree(e); 632 return RETURN_NULL(args); // other error 633 } 634 } 635 636 // auto-generate the "widgets" Wren module 637 const(char)* widgetModuleSource() nothrow 638 { 639 _widgetModuleSource.clearContents(); 640 641 void text(const(char)[] s) 642 { 643 _widgetModuleSource.pushBack(cast(char[])s); // const_cast here 644 } 645 646 void textZ(const(char)* s) 647 { 648 _widgetModuleSource.pushBack(cast(char[])s[0..strlen(s)]); // const_cast here 649 } 650 651 void LF() 652 { 653 _widgetModuleSource.pushBack('\n'); 654 } 655 656 text(`import "ui" for UIElement, RGBA`); LF; 657 658 foreach(size_t nthClass, ec; _exportedClasses[]) 659 { 660 text("class "); textZ(ec.wrenClassNameZ); text(" is UIElement {"); LF; 661 662 char[16] bufC; 663 snprintf(bufC.ptr, 16, "%d", cast(int)nthClass); 664 665 foreach(size_t nth, prop; ec.properties()) 666 { 667 char[16] buf; 668 snprintf(buf.ptr, 16, "%d", cast(int)nth); 669 670 bool isRGBA = prop.type == ScriptPropertyType.RGBA; 671 672 if (isRGBA) 673 { 674 // getter 675 // PERF: this calls the wren function 4 times 676 text(" "); text(prop.identifier); text("{"); LF; 677 text(" return RGBA.new( e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",0),"); 678 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",1),"); 679 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",2),"); 680 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",3))"); LF; 681 text(" }"); LF; 682 683 // setter for a RGBA property 684 text(" "); text(prop.identifier); text("=(c){"); LF; 685 text(" e.setPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",c.r, c.g, c.b, c.a)"); LF; 686 text(" }"); LF; 687 688 // same but return this for chaining syntax 689 text(" "); text(prop.identifier); text("(c){"); LF; 690 text(" e.setPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",c.r, c.g, c.b, c.a)"); LF; 691 text(" return this"); LF; 692 text(" }"); LF; 693 } 694 else 695 { 696 // getter 697 text(" "); text(prop.identifier); text("{"); LF; 698 text(" return e.getProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(")"); LF; 699 text(" }"); LF; 700 701 // setter for property (itself a Wren property setter) 702 text(" "); text(prop.identifier); text("=(x){"); LF; 703 text(" e.setProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",x)"); LF; 704 text(" }"); LF; 705 706 // same but return this for chaining syntax 707 text(" "); text(prop.identifier); text("(x){"); LF; 708 text(" e.setProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",x)"); LF; 709 text(" return this"); LF; 710 text(" }"); LF; 711 } 712 } 713 LF; 714 text("}"); LF; LF; 715 } 716 717 _widgetModuleSource.pushBack('\0'); 718 return _widgetModuleSource.ptr; 719 } 720 721 ScriptExportClass findExportedClassByClassInfo(TypeInfo_Class info) nothrow 722 { 723 // PERF: again, not a simple lookup bt O(num-classes) 724 foreach(ScriptExportClass sec; _exportedClasses[]) 725 { 726 if (sec.concreteClassInfo is info) 727 { 728 // Found 729 return sec; 730 } 731 } 732 return null; 733 } 734 735 // Add a single UIElement derivative class into the set of known classes in Wren 736 // Note that it enumerates all @ScriptProperty from its ancestors too, so it works. 737 // Wren code doesn't actually know that UIImageKnob is derived from UIKnob. 738 private void registerDClass(alias aClass)(ScriptExportClass classDesc) nothrow 739 { 740 foreach(memberName; __traits(allMembers, aClass)) 741 { 742 alias P = __traits(getMember, aClass, memberName); 743 foreach(attr; __traits(getAttributes, P)) 744 {{ 745 static if(is(attr == ScriptProperty)) 746 { 747 alias FieldType = typeof(P); 748 749 enum string fieldName = P.stringof; 750 enum size_t offsetInClass = P.offsetof; 751 752 ScriptPropertyDesc desc; 753 desc.identifier = fieldName; 754 desc.offset = offsetInClass; 755 static if (is(FieldType == enum)) 756 { 757 // Note: enum are just integers in Wren, no translation of enum value. 758 static if (FieldType.sizeof == 1) 759 desc.type = ScriptPropertyType.byte_; 760 else static if (FieldType.sizeof == 2) 761 desc.type = ScriptPropertyType.short_; 762 else static if (FieldType.sizeof == 4) 763 desc.type = ScriptPropertyType.int_; 764 else 765 static assert(false, "Unsupported enum size in @ScriptProperty field " ~ fieldName ~ " of type " ~ FieldType.stringof); 766 } 767 else static if (is(FieldType == bool)) 768 desc.type = ScriptPropertyType.bool_; 769 else static if (is(FieldType == RGBA)) 770 desc.type = ScriptPropertyType.RGBA; 771 else static if (is(FieldType == ubyte)) 772 desc.type = ScriptPropertyType.ubyte_; 773 else static if (is(FieldType == byte)) 774 desc.type = ScriptPropertyType.byte_; 775 else static if (is(FieldType == ushort)) 776 desc.type = ScriptPropertyType.ushort_; 777 else static if (is(FieldType == short)) 778 desc.type = ScriptPropertyType.short_; 779 else static if (is(FieldType == uint)) 780 desc.type = ScriptPropertyType.uint_; 781 else static if (is(FieldType == int)) 782 desc.type = ScriptPropertyType.int_; 783 else static if (is(FieldType == float)) 784 desc.type = ScriptPropertyType.float_; 785 else static if (is(FieldType == double)) 786 desc.type = ScriptPropertyType.double_; 787 else static if (is(FieldType == L16)) // Note: this is temporary. TODO L16 properties should be eventually replaced by ushort instead. 788 desc.type = ScriptPropertyType.ushort_; 789 else 790 static assert(false, "No @ScriptProperty support for field " ~ fieldName ~ " of type " ~ FieldType.stringof); // FUTURE: a way to add other types for properties? 791 792 classDesc.addProperty(desc); 793 } 794 }} 795 } 796 } 797 } 798 799 private: 800 801 802 void dplug_wrenPrint(WrenVM* vm, const(char)* text) 803 { 804 WrenSupport ws = cast(WrenSupport) vm.config.userData; 805 ws.print(text); 806 } 807 808 void dplug_wrenError(WrenVM* vm, WrenErrorType type, const(char)* module_, int line, const(char)* message) 809 { 810 WrenSupport ws = cast(WrenSupport) vm.config.userData; 811 ws.error(type, module_, line, message); 812 } 813 814 WrenForeignMethodFn dplug_wrenBindForeignMethod(WrenVM* vm, const(char)* module_, const(char)* className, bool isStatic, const(char)* signature) 815 { 816 WrenSupport ws = cast(WrenSupport) vm.config.userData; 817 return ws.foreignMethod(vm, module_, className, isStatic, signature); 818 } 819 820 WrenForeignClassMethods dplug_wrenBindForeignClass(WrenVM* vm, const(char)* module_, const(char)* className) 821 { 822 WrenSupport ws = cast(WrenSupport) vm.config.userData; 823 return ws.foreignClass(vm, module_, className); 824 } 825 826 WrenLoadModuleResult dplug_wrenLoadModule(WrenVM* vm, const(char)* name) 827 { 828 WrenSupport ws = cast(WrenSupport) vm.config.userData; 829 return ws.loadModule(vm, name); 830 } 831 832 bool dplug_wrenDollarOperator(WrenVM* vm, Value* args) 833 { 834 WrenSupport ws = cast(WrenSupport) vm.config.userData; 835 return ws.dollarOperator(vm, args); 836 } 837 838 839 // Bloom filter to filter out if an identifier is not defined, quickly. 840 // Using the 64-bit FNV-1 hash: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function 841 struct IdentifierBloom 842 { 843 pure nothrow @nogc @safe: 844 alias HashResult = ulong; 845 846 ulong bits = 0; 847 848 static HashResult hashOf(const(char)[] identifier) 849 { 850 ulong hash = 0xcbf29ce484222325; 851 852 foreach(char ch; identifier) 853 { 854 hash = hash * 0x00000100000001B3; 855 hash = hash ^ cast(ulong)(ch); 856 } 857 858 return hash; 859 } 860 861 bool couldContain(HashResult identifierHash) 862 { 863 return (_bits & identifierHash) == identifierHash; 864 } 865 866 void add(HashResult identifierHash) 867 { 868 _bits |= identifierHash; 869 } 870 871 private: 872 ulong _bits = 0; 873 }