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, isFunction; 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 WrenInterpretResult result = wrenInterpret(_vm, path, source); 237 return result == WrenInterpretResult.WREN_RESULT_SUCCESS; 238 } 239 240 /// Interpret arbitrary code. For advanced users only. 241 /// Returns: `true` if no runtime or compile-time error. 242 bool interpret(const(char)* source) nothrow 243 { 244 return interpret("", source); 245 } 246 247 // </advanced API> 248 249 ~this() nothrow 250 { 251 stopWrenVM(); 252 253 foreach(ps; _preloadedSources[]) 254 { 255 free(ps.moduleName); 256 free(ps.source); 257 } 258 259 foreach(fw; _fileSources[]) 260 { 261 free(fw.moduleName); 262 free(fw.wrenFilePath); 263 free(fw.lastSource); 264 } 265 266 foreach(ec; _exportedClasses[]) 267 { 268 destroyFree(ec); 269 } 270 } 271 272 package: 273 274 IUIContext uiContext() nothrow 275 { 276 return _uiContext; 277 } 278 279 ScriptPropertyDesc* getScriptProperty(int nthClass, int nthProp) nothrow 280 { 281 ScriptExportClass sec = _exportedClasses[nthClass]; 282 ScriptPropertyDesc[] descs = sec.properties(); 283 return &descs[nthProp]; 284 } 285 286 private: 287 288 WrenVM* _vm = null; 289 IUIContext _uiContext; 290 double _timeSinceLastScriptCheck = 0; // in seconds 291 IdentifierBloom _identifierBloom; 292 293 static struct PreloadedSource 294 { 295 char* moduleName; 296 char* source; 297 } 298 299 static struct FileWatch 300 { 301 nothrow: 302 @nogc: 303 char* moduleName; 304 char* wrenFilePath; 305 char* lastSource; 306 307 bool updateAndReturnIfChanged() 308 { 309 // FUTURE: eventually use stat to get date of change instead 310 char* newSource = readWrenFile(); 311 312 // Sometimes reading the file fails, and then we should just retry later. Report no changes. 313 if (newSource is null) 314 return false; 315 316 if ((lastSource is null) || strcmp(lastSource, newSource) != 0) 317 { 318 free(lastSource); 319 lastSource = newSource; 320 return true; 321 } 322 else 323 { 324 free(newSource); 325 return false; 326 } 327 } 328 329 char* readWrenFile() 330 { 331 ubyte[] content = readFile(wrenFilePath); 332 return cast(char*) content.ptr; // correct because readFile return one trailing '\0' 333 } 334 } 335 336 /// All known premade modules. 337 Vec!PreloadedSource _preloadedSources; 338 339 /// All known file-watch modules. 340 Vec!FileWatch _fileSources; 341 342 /// All known D @ScriptExport classes. 343 Vec!ScriptExportClass _exportedClasses; 344 345 /// "widgets" module source, recreated on import based upon _exportedClasses content. 346 Vec!char _widgetModuleSource; 347 348 /// The number of time a Wren VM has been started. This is to invalidate caching of Wren values. 349 uint _vmGeneration = 0; 350 351 /// Wren module, its look-up is cached to speed-up $ operator. 352 ObjModule* _cachedUIModule, 353 _cachedWidgetsModule; 354 355 // ui.Element class, its look-up is cached to speed-up $ operator. 356 ObjClass* _cachedClassElement; 357 358 void startWrenVM() nothrow 359 { 360 if (_vm !is null) 361 return; // Already started 362 363 WrenConfiguration config; 364 wrenInitConfiguration(&config); 365 366 config.writeFn = &dplug_wrenPrint; 367 config.errorFn = &dplug_wrenError; 368 config.dollarOperatorFn = &dplug_wrenDollarOperator; 369 config.bindForeignMethodFn = &dplug_wrenBindForeignMethod; 370 config.bindForeignClassFn = &dplug_wrenBindForeignClass; 371 config.loadModuleFn = &dplug_wrenLoadModule; 372 config.userData = cast(void*)this; 373 374 // Makes a bit easier to copy/paste code from D to Wren and vice-versa. 375 // Of course this isn't vanilla Wren, but too much time were spent chasing and 376 // eliminating semicolons in Wren code. 377 config.acceptsTrailingSemicolons = true; 378 379 // Note: wren defaults for memory usage make a lot of sense. 380 _vm = wrenNewVM(&config); 381 _vmGeneration++; 382 _cachedUIModule = null; 383 _cachedWidgetsModule = null; 384 _cachedClassElement = null; 385 } 386 387 void stopWrenVM() nothrow 388 { 389 if (_vm !is null) 390 { 391 wrenFreeVM(_vm); 392 _vm = null; 393 } 394 } 395 396 void print(const(char)* text) nothrow 397 { 398 debugLog(text); 399 } 400 401 void error(WrenErrorType type, const(char)* module_, int line, const(char)* message) nothrow 402 { 403 switch(type) 404 { 405 case WrenErrorType.WREN_ERROR_COMPILE: 406 debugLogf("%s(%d): Error: %s\n", module_, line, message); 407 break; 408 409 case WrenErrorType.WREN_ERROR_RUNTIME: 410 debugLogf("wren crash: %s\n", message); 411 break; 412 413 case WrenErrorType.WREN_ERROR_STACK_TRACE: 414 debugLogf(" %s.%s:%d\n", module_, message, line); 415 break; 416 417 default: 418 assert(false); 419 } 420 } 421 422 // this is called anytime Wren looks for a foreign method 423 WrenForeignMethodFn foreignMethod(WrenVM* vm, const(char)* module_, const(char)* className, bool isStatic, const(char)* signature) nothrow 424 { 425 // Introducing the Dplug-Wren standard library here. 426 if (strcmp(module_, "ui") == 0) 427 { 428 return wrenUIBindForeignMethod(vm, className, isStatic, signature); 429 } 430 return null; 431 } 432 433 // this is called anytime Wren looks for a foreign class 434 WrenForeignClassMethods foreignClass(WrenVM* vm, const(char)* module_, const(char)* className) nothrow 435 { 436 if (strcmp(module_, "ui") == 0) 437 return wrenUIForeignClass(vm, className); 438 439 WrenForeignClassMethods methods; 440 methods.allocate = null; 441 methods.finalize = null; 442 return methods; 443 } 444 445 WrenLoadModuleResult loadModule(WrenVM* vm, const(char)* name) nothrow 446 { 447 WrenLoadModuleResult res; 448 res.source = null; 449 res.onComplete = null; 450 res.userData = null; 451 452 // Try file-watch source first 453 foreach(fw; _fileSources[]) 454 { 455 if (strcmp(name, fw.moduleName) == 0) 456 { 457 // Should have parsed the file preventively. 458 // If you assert here, it probably means that your Wren file was 459 // passed with an incorrect absolute path. 460 assert(fw.lastSource); 461 462 res.source = fw.lastSource; 463 goto found; 464 } 465 } 466 467 // Try preloaded source first 468 foreach(ps; _preloadedSources[]) 469 { 470 if (strcmp(name, ps.moduleName) == 0) 471 { 472 res.source = ps.source; 473 goto found; 474 } 475 } 476 477 478 if (strcmp(name, "widgets") == 0) 479 res.source = widgetModuleSource(); 480 481 if (strcmp(name, "ui") == 0) 482 res.source = wrenUIModuleSource(); 483 found: 484 return res; 485 } 486 487 // Note: return a value whose lifetime is tied to Wren VM. 488 ObjModule* getWrenModule(const(char)* name) nothrow 489 { 490 Value moduleName = wrenStringFormat(_vm, "$", name); 491 wrenPushRoot(_vm, AS_OBJ(moduleName)); 492 ObjModule* wModule = getModule(_vm, moduleName); 493 wrenPopRoot(_vm); 494 return wModule; 495 } 496 497 ObjClass* getWrenClassForThisUIELement(UIElement elem) nothrow 498 { 499 // Do we have a valid cached ObjClass* inside the UIElement? 500 void* cachedClass = elem.getUserPointer(UIELEMENT_POINTERID_WREN_EXPORTED_CLASS); 501 if (cachedClass !is null) 502 { 503 // Same Wren VM? 504 uint cacheGen = cast(uint) elem.getUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION); 505 if (cacheGen == _vmGeneration) 506 { 507 // yes, reuse 508 return cast(ObjClass*) cachedClass; 509 } 510 } 511 512 enum UIELEMENT_POINTERID_WREN_EXPORTED_CLASS = 0; /// The cached Wren class of this UIElement. 513 enum UIELEMENT_POINTERID_WREN_VM_GENERATION = 1; /// The Wren VM count, as it is restarted. Stored as void*, but is an uint. 514 515 // try to find concrete class directly 516 ObjClass* classTarget; 517 ScriptExportClass concreteClassInfo = findExportedClassByClassInfo(elem.classinfo); 518 if (concreteClassInfo) 519 { 520 const(char)* wrenClassName = concreteClassInfo.wrenClassNameZ(); 521 classTarget = AS_CLASS(wrenFindVariable(_vm, _cachedWidgetsModule, wrenClassName)); 522 } 523 else 524 { 525 // $ return a UIElement, no classes were found 526 // This is a silent error, properties assignment will silently fail. 527 // We still cache that incorrect value, as the real fix is registering the class to Wren. 528 classTarget = AS_CLASS(wrenFindVariable(_vm, _cachedUIModule, "UIElement")); 529 } 530 531 // Cached return value inside the UIElement, which will speed-up future $ 532 elem.setUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION, classTarget); 533 elem.setUserPointer(UIELEMENT_POINTERID_WREN_VM_GENERATION, cast(void*) _vmGeneration); 534 return classTarget; 535 } 536 537 // Implementation of the $ operator. 538 bool dollarOperator(WrenVM* vm, Value* args) nothrow 539 { 540 if (!IS_STRING(args[0])) 541 return RETURN_NULL(args); 542 543 const(char)* id = AS_STRING(args[0]).value.ptr; 544 545 // Note: elem can be null here. If no element is found with a proper ID, 546 // return an unbound value. 547 UIElement elem = _uiContext.getElementById(id); 548 if (elem is null) 549 return RETURN_NULL(args); // $(id) not found 550 551 // Find a Wren class we have to convert it to. 552 // $ can be any of the imported classes in "widgets", if not it is an UIElement. 553 // Note that both "ui" and "widgets" module MUST be imported. 554 555 if (_cachedUIModule is null) 556 { 557 _cachedUIModule = getWrenModule("ui"); 558 if (_cachedUIModule is null) 559 return RETURN_ERROR(vm, "module \"ui\" is not imported"); 560 } 561 562 if (_cachedWidgetsModule is null) 563 { 564 _cachedWidgetsModule = getWrenModule("widgets"); 565 if (_cachedWidgetsModule is null) 566 return RETURN_ERROR(vm, "module \"widgets\" is not imported"); 567 } 568 569 ObjClass* classTarget = getWrenClassForThisUIELement(elem); 570 571 if (_cachedClassElement is null) 572 { 573 _cachedClassElement = AS_CLASS(wrenFindVariable(vm, _cachedUIModule, "Element")); 574 } 575 576 if (classTarget is null) 577 { 578 return RETURN_ERROR(vm, "cannot create a IUElement from operator $"); 579 } 580 581 Value obj = wrenNewInstance(vm, classTarget); 582 583 // PERF: could this be also cached? It wouldn't be managed by Wren. 584 // Create new Element foreign 585 ObjForeign* foreign = wrenNewForeign(vm, _cachedClassElement, UIElementBridge.sizeof); 586 UIElementBridge* bridge = cast(UIElementBridge*) foreign.data.ptr; 587 bridge.elem = elem; 588 589 // Assign it in the first field of the newly created ui.UIElement 590 ObjInstance* instance = AS_INSTANCE(obj); 591 instance.fields[0] = OBJ_VAL(foreign); 592 593 return RETURN_OBJ(args, AS_INSTANCE(obj)); 594 } 595 596 // auto-generate the "widgets" Wren module 597 const(char)* widgetModuleSource() nothrow 598 { 599 _widgetModuleSource.clearContents(); 600 601 void text(const(char)[] s) 602 { 603 _widgetModuleSource.pushBack(cast(char[])s); // const_cast here 604 } 605 606 void textZ(const(char)* s) 607 { 608 _widgetModuleSource.pushBack(cast(char[])s[0..strlen(s)]); // const_cast here 609 } 610 611 void LF() 612 { 613 _widgetModuleSource.pushBack('\n'); 614 } 615 616 text(`import "ui" for UIElement, RGBA`); LF; 617 618 foreach(size_t nthClass, ec; _exportedClasses[]) 619 { 620 text("class "); textZ(ec.wrenClassNameZ); text(" is UIElement {"); LF; 621 622 char[16] bufC; 623 snprintf(bufC.ptr, 16, "%d", cast(int)nthClass); 624 625 foreach(size_t nth, prop; ec.properties()) 626 { 627 char[16] buf; 628 snprintf(buf.ptr, 16, "%d", cast(int)nth); 629 630 bool isRGBA = prop.type == ScriptPropertyType.RGBA; 631 632 if (isRGBA) 633 { 634 // getter 635 // PERF: this calls the wren function 4 times 636 text(" "); text(prop.identifier); text("{"); LF; 637 text(" return RGBA.new( e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",0),"); 638 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",1),"); 639 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",2),"); 640 text("e.getPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",3))"); LF; 641 text(" }"); LF; 642 643 // setter for a RGBA property 644 text(" "); text(prop.identifier); text("=(c){"); LF; 645 text(" e.setPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",c.r, c.g, c.b, c.a)"); LF; 646 text(" }"); LF; 647 648 // same but return this for chaining syntax 649 text(" "); text(prop.identifier); text("(c){"); LF; 650 text(" e.setPropRGBA_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",c.r, c.g, c.b, c.a)"); LF; 651 text(" return this"); LF; 652 text(" }"); LF; 653 } 654 else 655 { 656 // getter 657 text(" "); text(prop.identifier); text("{"); LF; 658 text(" return e.getProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(")"); LF; 659 text(" }"); LF; 660 661 // setter for property (itself a Wren property setter) 662 text(" "); text(prop.identifier); text("=(x){"); LF; 663 text(" e.setProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",x)"); LF; 664 text(" }"); LF; 665 666 // same but return this for chaining syntax 667 text(" "); text(prop.identifier); text("(x){"); LF; 668 text(" e.setProp_("); textZ(bufC.ptr); text(","); textZ(buf.ptr); text(",x)"); LF; 669 text(" return this"); LF; 670 text(" }"); LF; 671 } 672 } 673 LF; 674 text("}"); LF; LF; 675 } 676 677 _widgetModuleSource.pushBack('\0'); 678 return _widgetModuleSource.ptr; 679 } 680 681 ScriptExportClass findExportedClassByClassInfo(TypeInfo_Class info) nothrow 682 { 683 // PERF: again, not a simple lookup bt O(num-classes) 684 foreach(ScriptExportClass sec; _exportedClasses[]) 685 { 686 if (sec.concreteClassInfo is info) 687 { 688 // Found 689 return sec; 690 } 691 } 692 return null; 693 } 694 695 // Add a single UIElement derivative class into the set of known classes in Wren 696 // Note that it enumerates all @ScriptProperty from its ancestors too, so it works. 697 // Wren code doesn't actually know that UIImageKnob is derived from UIKnob. 698 private void registerDClass(alias aClass)(ScriptExportClass classDesc) nothrow 699 { 700 foreach(memberName; __traits(allMembers, aClass)) 701 { 702 alias P = __traits(getMember, aClass, memberName); 703 704 static if (!isFunction!P) 705 {{ 706 foreach(attr; __traits(getAttributes, P)) 707 {{ 708 static if(is(attr == ScriptProperty)) 709 { 710 alias FieldType = typeof(P); 711 712 enum string fieldName = P.stringof; 713 enum size_t offsetInClass = P.offsetof; 714 715 ScriptPropertyDesc desc; 716 desc.identifier = fieldName; 717 desc.offset = offsetInClass; 718 static if (is(FieldType == enum)) 719 { 720 // Note: enum are just integers in Wren, no translation of enum value. 721 static if (FieldType.sizeof == 1) 722 desc.type = ScriptPropertyType.byte_; 723 else static if (FieldType.sizeof == 2) 724 desc.type = ScriptPropertyType.short_; 725 else static if (FieldType.sizeof == 4) 726 desc.type = ScriptPropertyType.int_; 727 else 728 static assert(false, "Unsupported enum size in @ScriptProperty field " ~ fieldName ~ " of type " ~ FieldType.stringof); 729 } 730 else static if (is(FieldType == bool)) 731 desc.type = ScriptPropertyType.bool_; 732 else static if (is(FieldType == RGBA)) 733 desc.type = ScriptPropertyType.RGBA; 734 else static if (is(FieldType == ubyte)) 735 desc.type = ScriptPropertyType.ubyte_; 736 else static if (is(FieldType == byte)) 737 desc.type = ScriptPropertyType.byte_; 738 else static if (is(FieldType == ushort)) 739 desc.type = ScriptPropertyType.ushort_; 740 else static if (is(FieldType == short)) 741 desc.type = ScriptPropertyType.short_; 742 else static if (is(FieldType == uint)) 743 desc.type = ScriptPropertyType.uint_; 744 else static if (is(FieldType == int)) 745 desc.type = ScriptPropertyType.int_; 746 else static if (is(FieldType == float)) 747 desc.type = ScriptPropertyType.float_; 748 else static if (is(FieldType == double)) 749 desc.type = ScriptPropertyType.double_; 750 else static if (is(FieldType == L16)) // Note: this is temporary. TODO L16 properties should be eventually replaced by ushort instead. 751 desc.type = ScriptPropertyType.ushort_; 752 else 753 static assert(false, "No @ScriptProperty support for field " ~ fieldName ~ " of type " ~ FieldType.stringof); // FUTURE: a way to add other types for properties? 754 755 classDesc.addProperty(desc); 756 } 757 }} 758 }} 759 } 760 } 761 } 762 763 private: 764 765 766 void dplug_wrenPrint(WrenVM* vm, const(char)* text) 767 { 768 WrenSupport ws = cast(WrenSupport) vm.config.userData; 769 ws.print(text); 770 } 771 772 void dplug_wrenError(WrenVM* vm, WrenErrorType type, const(char)* module_, int line, const(char)* message) 773 { 774 WrenSupport ws = cast(WrenSupport) vm.config.userData; 775 ws.error(type, module_, line, message); 776 } 777 778 WrenForeignMethodFn dplug_wrenBindForeignMethod(WrenVM* vm, const(char)* module_, const(char)* className, bool isStatic, const(char)* signature) 779 { 780 WrenSupport ws = cast(WrenSupport) vm.config.userData; 781 return ws.foreignMethod(vm, module_, className, isStatic, signature); 782 } 783 784 WrenForeignClassMethods dplug_wrenBindForeignClass(WrenVM* vm, const(char)* module_, const(char)* className) 785 { 786 WrenSupport ws = cast(WrenSupport) vm.config.userData; 787 return ws.foreignClass(vm, module_, className); 788 } 789 790 WrenLoadModuleResult dplug_wrenLoadModule(WrenVM* vm, const(char)* name) 791 { 792 WrenSupport ws = cast(WrenSupport) vm.config.userData; 793 return ws.loadModule(vm, name); 794 } 795 796 bool dplug_wrenDollarOperator(WrenVM* vm, Value* args) 797 { 798 WrenSupport ws = cast(WrenSupport) vm.config.userData; 799 return ws.dollarOperator(vm, args); 800 } 801 802 803 // Bloom filter to filter out if an identifier is not defined, quickly. 804 // Using the 64-bit FNV-1 hash: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function 805 struct IdentifierBloom 806 { 807 pure nothrow @nogc @safe: 808 alias HashResult = ulong; 809 810 ulong bits = 0; 811 812 static HashResult hashOf(const(char)[] identifier) 813 { 814 ulong hash = 0xcbf29ce484222325; 815 816 foreach(char ch; identifier) 817 { 818 hash = hash * 0x00000100000001B3; 819 hash = hash ^ cast(ulong)(ch); 820 } 821 822 return hash; 823 } 824 825 bool couldContain(HashResult identifierHash) 826 { 827 return (_bits & identifierHash) == identifierHash; 828 } 829 830 void add(HashResult identifierHash) 831 { 832 _bits |= identifierHash; 833 } 834 835 private: 836 ulong _bits = 0; 837 }