diff --git a/CHANGELOG.md b/CHANGELOG.md index c1969236..c75916f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [1.5.0] - 2022-02-?? ### Added -- Added the new `polymod.hscript.PolymodScriptClass` class, which contains utility functions that allow defining custom classes in scripts. +- Added new functionality which allows for parsing and instantiation of classes defined in scripts. See [Scripted Classes](https://polymod.io/docs/scripted-classes) for more information. +- Incorporated the functionality of `hscript-ex` into Polymod. +### Changed +- - These scripted classes can extend existing classes or even other scripted classes. - Parse any scripted classes with `registerAllScriptClasses()`, get the list of scripted classes extending a given class with `listScriptClassesExtendingClass()`, then instantiate it with `createScriptClassInstance(className, args)`. You can cast the `AbstractScriptClass` to the appropriate type once instantiated. - Note this interface is subject to change and may be deprecated in favor of a more seamless interface in the future. diff --git a/docs/_includes/toc.html b/docs/_includes/toc.html index 26afb623..395d3aae 100644 --- a/docs/_includes/toc.html +++ b/docs/_includes/toc.html @@ -65,6 +65,14 @@

Documentation

href="{{ '/docs/scripting' | relative_url }}"> Scripting +
  • 1000) { + abc = 20; + } + + trace('Value is: ' + abc); + } } ``` @@ -24,15 +90,18 @@ class ScriptedStage extends Stage implements HScriptable { There are many things which you can do within a scripted class, including but not limited to: * Override the constructor (using `public function new() { ... }`) -* Import modules and instantiate objects (like `import flixel.FlxG`). -* Access public or private fields of the superclass, including functions. +* Define new fields and functions and call them from other functions. +* Modify public or private values of the superclass, and call superclass functions. +* Override existing functions of the class to replace (or extend) functionality. +* Import modules and call static functions or instantiate objects (like `import flixel.FlxG` or `import flixel.FlxSprite`) There are some things to watch out for though: -* You can't override `static` functions in a script. +* You can't override `static` functions from a script. * You can't override a function which uses a private class as an argument. * This is not possible in any Haxe program so don't worry about this. -* You can't (CURRENTLY) override functions with an optional argument like `function create(?name:String = 'test')` +* You can't use string interpolation from a script. + * For example, `trace('Value is ${abc}')` will actually print `Value is ${abc}` instead of `Value is 123`. + * Thankfully, you can still use string concatenation, for example, `trace('Value is ' + abc)`. * You can't (CURRENTLY) override functions which utilize a constrained generic type like `function test>(a:T)` - * I think fully generic types work? Haven't tested. -* Sometimes using `this.xyz` to access fields doesn't work, but just `xyz` does. +* You can't (CURRENTLY) override functions with an optional argument like `function create(?name:String = 'test')` diff --git a/polymod/Polymod.hx b/polymod/Polymod.hx index b8f1ca19..7c8c427a 100644 --- a/polymod/Polymod.hx +++ b/polymod/Polymod.hx @@ -278,8 +278,7 @@ class Polymod // Store the params for later use (by loadMod, unloadMod, and clearMods) prevParams = params; - // Do scripted class initialization now that the assetLibrary is - #if hscript_ex + // Do scripted class initialization now that the assetLibrary is loaded. if (params.useScriptedClasses) { Polymod.notice(PolymodErrorCode.SCRIPT_CLASS_PARSING, 'Parsing script classes...'); PolymodScriptClass.registerAllScriptClasses(); @@ -287,12 +286,6 @@ class Polymod var classList = PolymodScriptClass.listScriptClasses(); Polymod.notice(PolymodErrorCode.SCRIPT_CLASS_PARSED, 'Parsed and registered ${classList.length} scripted classes.'); } - #else - if (params.useScriptedClasses) { - Polymod.error(PolymodErrorCode.SCRIPT_NO_INTERPRETER, - 'Polymod does not support useScriptedClasses unless the library "hscript-ex" is installed.'); - } - #end return modMeta; } diff --git a/polymod/PolymodConfig.hx b/polymod/PolymodConfig.hx index 2f2a18e5..c0aa26fd 100644 --- a/polymod/PolymodConfig.hx +++ b/polymod/PolymodConfig.hx @@ -20,7 +20,7 @@ class PolymodConfig */ public static var debug(get, default):Null; - static function get_debug() + static function get_debug():Null { // If the value is null, retrieve the value as a Haxe define. if (debug == null) @@ -38,7 +38,7 @@ class PolymodConfig */ public static var rootPath(get, default):String; - static function get_rootPath() + static function get_rootPath():String { // If the value is null, retrieve the value as a Haxe define. if (rootPath == null) @@ -58,7 +58,7 @@ class PolymodConfig */ public static var useNamespaceInPaths(get, default):Null; - static function get_useNamespaceInPaths() + static function get_useNamespaceInPaths():Null { // If the value is null, retrieve the value as a Haxe define. if (useNamespaceInPaths == null) @@ -76,7 +76,7 @@ class PolymodConfig */ public static var scriptExt(get, default):String; - static function get_scriptExt() + static function get_scriptExt():String { // If the value is null, retrieve the value as a Haxe define. if (scriptExt == null) @@ -94,7 +94,7 @@ class PolymodConfig */ public static var scriptClassExt(get, default):String; - static function get_scriptClassExt() + static function get_scriptClassExt():String { // If the value is null, retrieve the value as a Haxe define. if (scriptClassExt == null) @@ -113,7 +113,7 @@ class PolymodConfig */ public static var scriptLibrary(get, default):String; - static function get_scriptLibrary() + static function get_scriptLibrary():String { // If the value is null, retrieve the value as a Haxe define. if (scriptLibrary == null) @@ -131,7 +131,7 @@ class PolymodConfig */ public static var appendFolder(get, default):String; - static function get_appendFolder() + static function get_appendFolder():String { // If the value is null, retrieve the value as a Haxe define. if (appendFolder == null) @@ -155,7 +155,7 @@ class PolymodConfig */ public static var apiVersionMatch(get, default):Null = null; - static function get_apiVersionMatch() + static function get_apiVersionMatch():Null { // If the value is null, retrieve the value as a Haxe define. if (apiVersionMatch == null) @@ -173,7 +173,7 @@ class PolymodConfig */ public static var mergeFolder(get, default):String; - static function get_mergeFolder() + static function get_mergeFolder():String { // If the value is null, retrieve the value as a Haxe define. if (mergeFolder == null) @@ -192,7 +192,7 @@ class PolymodConfig // @:deprecated("Functionality removed, new implementation pending") public static var modPackFile(get, default):String; - static function get_modPackFile() + static function get_modPackFile():String { // If the value is null, retrieve the value as a Haxe define. if (modPackFile == null) @@ -210,7 +210,7 @@ class PolymodConfig */ public static var modMetadataFile(get, default):String; - static function get_modMetadataFile() + static function get_modMetadataFile():String { // If the value is null, retrieve the value as a Haxe define. if (modMetadataFile == null) @@ -228,7 +228,7 @@ class PolymodConfig */ public static var modIconFile(get, default):String; - static function get_modIconFile() + static function get_modIconFile():String { // If the value is null, retrieve the value as a Haxe define. if (modIconFile == null) @@ -247,7 +247,7 @@ class PolymodConfig */ public static var modIgnoreFiles(get, default):Array; - static function get_modIgnoreFiles() + static function get_modIgnoreFiles():Array { // If the value is null, retrieve the value as a Haxe define. if (modIgnoreFiles == null) diff --git a/polymod/backends/PolymodAssetLibrary.hx b/polymod/backends/PolymodAssetLibrary.hx index 40a03db6..83ed0f0b 100644 --- a/polymod/backends/PolymodAssetLibrary.hx +++ b/polymod/backends/PolymodAssetLibrary.hx @@ -137,6 +137,7 @@ class PolymodAssetLibrary backend.destroy(); } PolymodScriptClass.clearScriptClasses(); + polymod.hscript.HScriptable.ScriptRunner.clearScripts(); } public function mergeAndAppendText(id:String, modText:String):String diff --git a/polymod/hscript/HScriptMacro.hx b/polymod/hscript/HScriptMacro.hx index 17abbfb5..c3d31d28 100644 --- a/polymod/hscript/HScriptMacro.hx +++ b/polymod/hscript/HScriptMacro.hx @@ -323,6 +323,7 @@ class HScriptMacro var hscriptCancellable:Bool = hscriptParams.cancellable == null ? HScriptParams.CANCELLABLE_DEFAULT : hscriptParams.cancellable; var hscriptOptional:Bool = hscriptParams.optional == null ? HScriptParams.OPTIONAL_DEFAULT : hscriptParams.optional; var hscriptRunBefore:Bool = hscriptParams.runBefore == null ? HScriptParams.RUN_BEFORE_DEFAULT : hscriptParams.runBefore; + var hscriptDynamicPath:Bool = hscriptParams.pathNameDynId != null; func.expr = macro { $b{hscriptRunBefore ? [func.expr] : []}; @@ -399,11 +400,14 @@ class HScriptMacro { constructor_setup = [macro _polymod_scripts = new polymod.hscript.HScriptable.ScriptRunner()]; } - constructor_setup.push(macro - { - polymod.Polymod.debug('Loading hscript ' + $v{pathName}); - _polymod_scripts.load($v{pathName}, Assets); - }); + if (!hscriptDynamicPath) + { + constructor_setup.push(macro + { + // polymod.Polymod.debug('Loading hscript ' + $v{pathName}); + _polymod_scripts.load($v{pathName}, Assets); + }); + } default: Context.error("Error: The @:hscript meta is only allowed on functions", Context.currentPos()); @@ -499,22 +503,22 @@ class HScriptMacro for (arg in args) {name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)} ]; - // Create scripted class utility functions. - var utilFields:Array = buildScriptedClassUtils(cls, superCls, constArgs); - fields = fields.concat(utilFields); + // Create scripted class utility functions. + var utilFields:Array = buildScriptedClassUtils(cls, superCls, constArgs); + fields = fields.concat(utilFields); constructor = buildScriptedClassConstructor(constArgs); case TLazy(builder): switch (builder()) { case TFun(args, ret): - // Build a new constructor, which has the same signature as the superclass constructor. + // Build a new constructor, which has the same signature as the superclass constructor. var constArgs = [ for (arg in args) {name: arg.name, opt: arg.opt, type: Context.toComplexType(arg.t)} ]; - // Create scripted class utility functions. - var utilFields:Array = buildScriptedClassUtils(cls, superCls, constArgs); - fields = fields.concat(utilFields); + // Create scripted class utility functions. + var utilFields:Array = buildScriptedClassUtils(cls, superCls, constArgs); + fields = fields.concat(utilFields); constructor = buildScriptedClassConstructor(constArgs); default: Context.error('Error: Lazy superclass constructor is not a function (got ${superCls.constructor.get().type})', @@ -547,14 +551,14 @@ class HScriptMacro { Context.info('Building scripted class utils', Context.currentPos()); var clsTypeName:String = cls.pack.join('.') != '' ? '${cls.pack.join('.')}.${cls.name}' : cls.name; - var superClsTypeName:String = superCls.pack.join('.') != '' ? '${superCls.pack.join('.')}.${superCls.name}' : superCls.name; + var superClsTypeName:String = superCls.pack.join('.') != '' ? '${superCls.pack.join('.')}.${superCls.name}' : superCls.name; // var _asc:AbstractScriptClass = null; var var__asc:Field = { name: '_asc', doc: "The AbstractScriptClass instance which any variable or function calls are redirected to internally.", access: [APrivate], // Private instance variable - kind: FVar(Context.toComplexType(Context.getType('hscript.AbstractScriptClass'))), + kind: FVar(Context.toComplexType(Context.getType('polymod.hscript.PolymodAbstractScriptClass'))), pos: cls.pos, }; @@ -593,15 +597,16 @@ class HScriptMacro args: [{name: 'clsName', type: Context.toComplexType(Context.getType('String'))},].concat(superConstArgs), params: null, ret: Context.toComplexType(Context.getType(clsTypeName)), - expr: macro { - polymod.hscript.PolymodScriptClass.scriptClassOverrides.set($v{superClsTypeName}, Type.resolveClass($v{clsTypeName})); + expr: macro + { + polymod.hscript.PolymodScriptClass.scriptClassOverrides.set($v{superClsTypeName}, Type.resolveClass($v{clsTypeName})); + + var asc:polymod.hscript.PolymodAbstractScriptClass = polymod.hscript.PolymodScriptClass.createScriptClassInstance(clsName, $a{constArgs}); + var scriptedObj = asc.superClass; - var asc:hscript.AbstractScriptClass = polymod.hscript.PolymodScriptClass.createScriptClassInstance(clsName, $a{constArgs}); - var scriptedObj = asc.superClass; + Reflect.setField(scriptedObj, '_asc', asc); - Reflect.setField(scriptedObj, '_asc', asc); - - return scriptedObj; + return scriptedObj; }, }), }; @@ -611,281 +616,323 @@ class HScriptMacro /** * For each function in the superclass, create a function in the subclass - * that redirects to the internal abstract script class. + * that redirects to the internal abstract script class. */ static function buildScriptedClassFieldOverrides(cls:haxe.macro.Type.ClassType):Array { - var fieldDone:Array = []; - var fieldArray:Array = []; - - var targetClass:haxe.macro.Type.ClassType = cls; - var tClass = Context.toComplexType(Context.getType(cls.name)); - - // Start with a custom implementation of .toString() - var func_toString:Field = buildScriptedClass_toString(targetClass); - fieldArray.push(func_toString); - fieldDone.push('toString'); - - while (targetClass != null) { - Context.info('Processing overrides for class: ${targetClass.name}', Context.currentPos()); - var newFields:Array = buildScriptedClassFieldOverrides_inner(targetClass); - for (newField in newFields) { - if (newField != null && !fieldDone.contains(newField.name)) { - // Context.info(' Registering: ${newField.name}', Context.currentPos()); - fieldDone.push(newField.name); - fieldArray.push(newField); - } else { - // Context.info('Redundant: ${newField.name}', Context.currentPos()); - } - } - // Context.info('Moving on... ${targetClass.superClass}', Context.currentPos()); - if (targetClass.superClass != null) { - targetClass = targetClass.superClass.t.get(); - } else { - targetClass = null; - } - } + var fieldDone:Array = []; + var fieldArray:Array = []; + + var targetClass:haxe.macro.Type.ClassType = cls; + var tClass = Context.toComplexType(Context.getType(cls.name)); + + // Start with a custom implementation of .toString() + var func_toString:Field = buildScriptedClass_toString(targetClass); + fieldArray.push(func_toString); + fieldDone.push('toString'); + + while (targetClass != null) + { + Context.info('Processing overrides for class: ${targetClass.name}', Context.currentPos()); + var newFields:Array = buildScriptedClassFieldOverrides_inner(targetClass); + for (newField in newFields) + { + if (newField != null && !fieldDone.contains(newField.name)) + { + // Context.info(' Registering: ${newField.name}', Context.currentPos()); + fieldDone.push(newField.name); + fieldArray.push(newField); + } + else + { + // Context.info('Redundant: ${newField.name}', Context.currentPos()); + } + } + // Context.info('Moving on... ${targetClass.superClass}', Context.currentPos()); + if (targetClass.superClass != null) + { + targetClass = targetClass.superClass.t.get(); + } + else + { + targetClass = null; + } + } return fieldArray; } - static function buildScriptedClass_toString(cls:haxe.macro.Type.ClassType):Field { - return { - name: 'toString', - doc: null, - access: [APublic, AOverride], - meta: null, - pos: cls.pos, - kind: FFun({ - args: [], - params: null, - ret: Context.toComplexType(Context.getType('String')), - expr: macro { - if (_asc == null) { - var clsName = $v{cls.name}; - var superName = $v{cls.superClass.t.get().name}; - return 'PolymodScriptedClass<${clsName} extends ${superName}>(NO ASC)'; - } else { - return _asc.callFunction('toString', []); - } - }, - }), - }; - } - - static function buildScriptedClassFieldOverrides_inner(cls:haxe.macro.Type.ClassType):Array { - var fields:Array = new Array(); - - // Context.info('Mapping overrides of class: ${cls.name}', Context.currentPos()); - for (field in cls.fields.get()) { - // Context.info('Attempting to build instance override: ${field.name}', Context.currentPos()); - if (field.name == 'new') { - // Do nothing - } else { - fields = fields.concat(overrideField(field, false)); - } - } - for (field in cls.statics.get()) { - // Context.info('Attempting to build static override: ${field.name}', Context.currentPos()); - - // TODO: Figure out a workaround for this error in order to support static fields - // HScriptMacro.hx: Cannot access _asc in static function - // fields.push(overrideField(field, true)); - } - - return fields.filter(function(field:Field):Bool { - return field != null; - }); - } - - static function overrideField(field:haxe.macro.Type.ClassField, isStatic:Bool, ?type:haxe.macro.Type = null):Array { - if (type == null) { - type = field.type; - } - - switch(type) { - case TLazy(lt): - // A lazy wrapper for another field. - // We have to call the function to get the true value. - var ltv:haxe.macro.Type = lt(); - // Context.info('Lazy field type: ${field}', Context.currentPos()); - return overrideField(field, isStatic, ltv); - // return []; - case TFun(args, ret): - // This field is a function of the class. - // We need to redirect to the scripted class in case our scripted class overrides it. - // If it isn't overridden, the AbstractScriptClass will call the original function. - - // We need to skip overriding functions which meet the following: - // 1. One or more argments are private types. - // 2. The function is an inline function. - // Neither scripted NOR normal classes can override these functions anyway, so it is safe to skip them. - // TODO: We are also skipping functions which take Null as an argument, since the type building function isn't handling them properly. - - for (arg in args) { - switch (arg.t) { - case TInst(ty, pa): - var typ = ty.get(); - if (typ.isPrivate) { - Context.info(' Skipping: "${field.name}" contains private type ${typ.module}.${typ.name}', Context.currentPos()); - return []; - } - default: - // Do nothing. - } - } - switch (field.kind) { - case FMethod(k): - switch(k) { - case MethInline: - Context.info(' Skipping: "${field.name}" is inline function', Context.currentPos()); - return []; - default: - // Do nothing. - } - default: - // Do nothing. - } - - var func_inputArgs:Array = []; - - // We only get limited information about the args from Type, we need to use TypedExprDef. - switch (field.expr().expr) { - case TFunction(tfunc): - for (arg in tfunc.args) { - var isOptional = (arg.value != null); - var tfuncMeta:haxe.macro.Metadata = arg.v.meta.get(); - var tfuncExpr:haxe.macro.Expr = arg.value == null ? null : Context.getTypedExpr(arg.value); - var tfuncType:haxe.macro.ComplexType = null; - if (isOptional) { - Context.info(' Skipping: FIX THIS "${field.name}" has an optional argument', Context.currentPos()); - return []; - // Convert haxe.macro.Type(T) to haxe.macro.Type(Null) - // There has to be a better way of doing this... - var exprStr:String = 'var n:Null<${arg.v.t.toString()}>;'; - var expr:haxe.macro.Expr = macro { - $e{Context.parse(exprStr, Context.currentPos())}; - n; - }; - var nullType = Context.typeof(expr); - // TODO: Remove once we get the fix for optional arguments working. - // tfuncType = { - // name: "Null", - // module: "StdTypes", - // params: [Context.toComplexType(arg.v.t)], - // }; - tfuncType = Context.toComplexType(nullType); - } else { - tfuncType = Context.toComplexType(arg.v.t); - } - var tfuncArg:FunctionArg = { - name: arg.v.name, - type: tfuncType, - opt: isOptional, - meta: tfuncMeta, - value: tfuncExpr, - }; - func_inputArgs.push(tfuncArg); - } - default: - Context.error('Expected a function and got ${field.expr().expr}', Context.currentPos()); - } - - // Is there a better way to do this? - var doesReturnVoid:Bool = ret.toString() == "Void"; - - // Generate the list of call arguments for the function. - var func_callArgs:Array = [for (arg in args) macro $i{arg.name}]; - var func_access = [field.isPublic ? APublic : APrivate]; - if (field.isFinal) func_access.push(AFinal); - if (isStatic) { - func_access.push(AStatic); - } else { - func_access.push(AOverride); - } - - // TODO: This breaks if there's a type constraint on the parameter. - var func_params = [for (param in field.params) { name: param.name } ]; - - var funcName:String = field.name; - var func_over:Field = { - name: funcName, - doc: field.doc == null ? 'Polymod ScriptedClass override of ${field.name}.' - : 'Polymod ScriptedClass override of ${field.name}.\n${field.doc}', - access: func_access, - meta: field.meta.get(), - pos: field.pos, - kind: FFun({ - args: func_inputArgs, - params: func_params, - ret: doesReturnVoid ? null : Context.toComplexType(ret), - expr: macro { - var fieldName:String = $v{funcName}; - if (_asc != null) { - // trace('ASC: Calling $fieldName() in macro-generated function...'); - return _asc.callFunction(fieldName, [$a{func_callArgs}]); - } else { - // Fallback, call the original function. - // trace('ASC: Fallback to original ${fieldName}'); - return super.$funcName($a{func_callArgs}); - } - }, - }), - }; - var func_superCall:Field = { - name: '__super_' + funcName, - doc: 'Calls the original ${field.name} function while ignoring the ScriptedClass override.', - access: [APrivate], - meta: field.meta.get(), - pos: field.pos, - kind: FFun({ - args: func_inputArgs, - params: func_params, - ret: doesReturnVoid ? null : Context.toComplexType(ret), - expr: macro { - var fieldName:String = $v{funcName}; - // Fallback, call the original function. - // trace('ASC: Force call to super ${fieldName}'); - return super.$funcName($a{func_callArgs}); - }, - }), - } - - return [func_over, func_superCall]; - case TInst(_t, _params): - // This field is an instance of a class. - // Example: var test:TestClass = new TestClass(); - - // Originally, I planned to replace all variables on the class with properties, - // however this is not possible because properties are merely a compile-time feature. - - // However, since scripted classes correctly access the superclass variables anyway, - // there is no need to override the value. - // Context.info('Field: Instance variable "${field.name}"', Context.currentPos()); - return []; - case TEnum(_t, _params): - // Enum instance - // Context.info('Field: Enum variable "${field.name}"', Context.currentPos()); - return []; - case TMono(_t): - // Monomorph instance - // https://haxe.org/manual/types-monomorph.html - // Context.info('Field: Monomorph variable "${field.name}"', Context.currentPos()); - return []; - case TAnonymous(_t): - // Context.info('Field: Anonymous variable "${field.name}"', Context.currentPos()); - return []; - case TDynamic(_t): - // Context.info('Field: Dynamic variable "${field.name}"', Context.currentPos()); - return []; - case TAbstract(_t, _params): - // Context.info('Field: Abstract variable "${field.name}"', Context.currentPos()); - return []; - default: - // Context.info('Unknown field type: ${field}', Context.currentPos()); - return []; - } - } + static function buildScriptedClass_toString(cls:haxe.macro.Type.ClassType):Field + { + return { + name: 'toString', + doc: null, + access: [APublic, AOverride], + meta: null, + pos: cls.pos, + kind: FFun({ + args: [], + params: null, + ret: Context.toComplexType(Context.getType('String')), + expr: macro + { + if (_asc == null) + { + var clsName = $v{cls.name}; + var superName = $v{cls.superClass.t.get().name}; + return 'PolymodScriptedClass<${clsName} extends ${superName}>(NO ASC)'; + } + else + { + return _asc.callFunction('toString', []); + } + }, + }), + }; + } + + static function buildScriptedClassFieldOverrides_inner(cls:haxe.macro.Type.ClassType):Array + { + var fields:Array = new Array(); + + // Context.info('Mapping overrides of class: ${cls.name}', Context.currentPos()); + for (field in cls.fields.get()) + { + // Context.info('Attempting to build instance override: ${field.name}', Context.currentPos()); + if (field.name == 'new') + { + // Do nothing + } + else + { + fields = fields.concat(overrideField(field, false)); + } + } + for (field in cls.statics.get()) + { + // Context.info('Attempting to build static override: ${field.name}', Context.currentPos()); + + // TODO: Figure out a workaround for this error in order to support static fields + // HScriptMacro.hx: Cannot access _asc in static function + // fields.push(overrideField(field, true)); + } + + return fields.filter(function(field:Field):Bool + { + return field != null; + }); + } + + static function overrideField(field:haxe.macro.Type.ClassField, isStatic:Bool, ?type:haxe.macro.Type = null):Array + { + if (type == null) + { + type = field.type; + } + + switch (type) + { + case TLazy(lt): + // A lazy wrapper for another field. + // We have to call the function to get the true value. + var ltv:haxe.macro.Type = lt(); + // Context.info('Lazy field type: ${field}', Context.currentPos()); + return overrideField(field, isStatic, ltv); + // return []; + case TFun(args, ret): + // This field is a function of the class. + // We need to redirect to the scripted class in case our scripted class overrides it. + // If it isn't overridden, the AbstractScriptClass will call the original function. + + // We need to skip overriding functions which meet the following: + // 1. One or more argments are private types. + // 2. The function is an inline function. + // Neither scripted NOR normal classes can override these functions anyway, so it is safe to skip them. + // TODO: We are also skipping functions which take Null as an argument, since the type building function isn't handling them properly. + + for (arg in args) + { + switch (arg.t) + { + case TInst(ty, pa): + var typ = ty.get(); + if (typ.isPrivate) + { + Context.info(' Skipping: "${field.name}" contains private type ${typ.module}.${typ.name}', Context.currentPos()); + return []; + } + default: + // Do nothing. + } + } + switch (field.kind) + { + case FMethod(k): + switch (k) + { + case MethInline: + Context.info(' Skipping: "${field.name}" is inline function', Context.currentPos()); + return []; + default: + // Do nothing. + } + default: + // Do nothing. + } + + var func_inputArgs:Array = []; + + // We only get limited information about the args from Type, we need to use TypedExprDef. + switch (field.expr().expr) + { + case TFunction(tfunc): + for (arg in tfunc.args) + { + var isOptional = (arg.value != null); + var tfuncMeta:haxe.macro.Metadata = arg.v.meta.get(); + var tfuncExpr:haxe.macro.Expr = arg.value == null ? null : Context.getTypedExpr(arg.value); + var tfuncType:haxe.macro.ComplexType = null; + if (isOptional) + { + Context.info(' Skipping: FIX THIS "${field.name}" has an optional argument', Context.currentPos()); + return []; + // Convert haxe.macro.Type(T) to haxe.macro.Type(Null) + // There has to be a better way of doing this... + var exprStr:String = 'var n:Null<${arg.v.t.toString()}>;'; + var expr:haxe.macro.Expr = macro + { + $e{Context.parse(exprStr, Context.currentPos())}; + n; + }; + var nullType = Context.typeof(expr); + // TODO: Remove once we get the fix for optional arguments working. + // tfuncType = { + // name: "Null", + // module: "StdTypes", + // params: [Context.toComplexType(arg.v.t)], + // }; + tfuncType = Context.toComplexType(nullType); + } + else + { + tfuncType = Context.toComplexType(arg.v.t); + } + var tfuncArg:FunctionArg = { + name: arg.v.name, + type: tfuncType, + opt: isOptional, + meta: tfuncMeta, + value: tfuncExpr, + }; + func_inputArgs.push(tfuncArg); + } + default: + Context.error('Expected a function and got ${field.expr().expr}', Context.currentPos()); + } + + // Is there a better way to do this? + var doesReturnVoid:Bool = ret.toString() == "Void"; + + // Generate the list of call arguments for the function. + var func_callArgs:Array = [for (arg in args) macro $i{arg.name}]; + var func_access = [field.isPublic ? APublic : APrivate]; + if (field.isFinal) + func_access.push(AFinal); + if (isStatic) + { + func_access.push(AStatic); + } + else + { + func_access.push(AOverride); + } + + // TODO: This breaks if there's a type constraint on the parameter. + var func_params = [for (param in field.params) {name: param.name}]; + + var funcName:String = field.name; + var func_over:Field = { + name: funcName, + doc: field.doc == null ? 'Polymod ScriptedClass override of ${field.name}.' : 'Polymod ScriptedClass override of ${field.name}.\n${field.doc}', + access: func_access, + meta: field.meta.get(), + pos: field.pos, + kind: FFun({ + args: func_inputArgs, + params: func_params, + ret: doesReturnVoid ? null : Context.toComplexType(ret), + expr: macro + { + var fieldName:String = $v{funcName}; + if (_asc != null) + { + // trace('ASC: Calling $fieldName() in macro-generated function...'); + return _asc.callFunction(fieldName, [$a{func_callArgs}]); + } + else + { + // Fallback, call the original function. + // trace('ASC: Fallback to original ${fieldName}'); + return super.$funcName($a{func_callArgs}); + } + }, + }), + }; + var func_superCall:Field = { + name: '__super_' + funcName, + doc: 'Calls the original ${field.name} function while ignoring the ScriptedClass override.', + access: [APrivate], + meta: field.meta.get(), + pos: field.pos, + kind: FFun({ + args: func_inputArgs, + params: func_params, + ret: doesReturnVoid ? null : Context.toComplexType(ret), + expr: macro + { + var fieldName:String = $v{funcName}; + // Fallback, call the original function. + // trace('ASC: Force call to super ${fieldName}'); + return super.$funcName($a{func_callArgs}); + }, + }), + } + + return [func_over, func_superCall]; + case TInst(_t, _params): + // This field is an instance of a class. + // Example: var test:TestClass = new TestClass(); + + // Originally, I planned to replace all variables on the class with properties, + // however this is not possible because properties are merely a compile-time feature. + + // However, since scripted classes correctly access the superclass variables anyway, + // there is no need to override the value. + // Context.info('Field: Instance variable "${field.name}"', Context.currentPos()); + return []; + case TEnum(_t, _params): + // Enum instance + // Context.info('Field: Enum variable "${field.name}"', Context.currentPos()); + return []; + case TMono(_t): + // Monomorph instance + // https://haxe.org/manual/types-monomorph.html + // Context.info('Field: Monomorph variable "${field.name}"', Context.currentPos()); + return []; + case TAnonymous(_t): + // Context.info('Field: Anonymous variable "${field.name}"', Context.currentPos()); + return []; + case TDynamic(_t): + // Context.info('Field: Dynamic variable "${field.name}"', Context.currentPos()); + return []; + case TAbstract(_t, _params): + // Context.info('Field: Abstract variable "${field.name}"', Context.currentPos()); + return []; + default: + // Context.info('Unknown field type: ${field}', Context.currentPos()); + return []; + } + } static function buildScriptedClassConstructor(superConstArgs:Array):Field { @@ -898,10 +945,10 @@ class HScriptMacro // }; // var constArgs:Array = [ascArg].concat(superConstArgs); - var constArgs:Array = superConstArgs; + var constArgs:Array = superConstArgs; var superCallArgs:Array = [for (arg in superConstArgs) macro $i{arg.name}]; - // Context.info(' Generating constructor for scripted class with super(${superCallArgs})', Context.currentPos()); + // Context.info(' Generating constructor for scripted class with super(${superCallArgs})', Context.currentPos()); return { name: 'new', diff --git a/polymod/hscript/HScriptable.hx b/polymod/hscript/HScriptable.hx index bd68aa8a..d4446821 100644 --- a/polymod/hscript/HScriptable.hx +++ b/polymod/hscript/HScriptable.hx @@ -196,13 +196,19 @@ typedef ScriptOutput = class ScriptRunner { - private var scripts:Map; + /** + * No reason not to make this static! Load a script once instead of 50 times. + */ + private static var scripts:Map = new Map(); public function new() { - scripts = new Map(); } + public static function clearScripts():Void { + scripts.clear(); + } + public function load(name:String, assetHandler:Dynamic):Script { if (assetHandler == null) @@ -274,20 +280,12 @@ class Script public static function buildParser():hscript.Parser { - #if hscript_ex - return new hscript.ParserEx(); - #else - return new hscript.Parser(); - #end + return new polymod.hscript.PolymodParserEx(); } public static function buildInterp():hscript.Interp { - #if hscript_ex - return new hscript.InterpEx(); - #else - return new hscript.Interp(); - #end + return new hscript.Interp(); } public function new(script:String) diff --git a/polymod/hscript/PolymodAbstractScriptClass.hx b/polymod/hscript/PolymodAbstractScriptClass.hx index 43ec1a47..f07f3d09 100644 --- a/polymod/hscript/PolymodAbstractScriptClass.hx +++ b/polymod/hscript/PolymodAbstractScriptClass.hx @@ -45,8 +45,8 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass return Reflect.getProperty(this.superClass, name); } else if (Reflect.hasField(this.superClass, name)) { return Reflect.field(this.superClass, name); - } else if (this.superClass != null && Std.is(this.superClass, ScriptClass)) { - var superScriptClass:AbstractScriptClass = cast(this.superClass, ScriptClass); + } else if (this.superClass != null && Std.isOfType(this.superClass, PolymodScriptClass)) { + var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); try { return superScriptClass.fieldRead(name); } catch (e:Dynamic) { } @@ -73,8 +73,8 @@ abstract PolymodAbstractScriptClass(PolymodScriptClass) from PolymodScriptClass } else if (Reflect.hasField(this.superClass, name)) { Reflect.setProperty(this.superClass, name, value); return value; - } else if (this.superClass != null && Std.is(this.superClass, ScriptClass)) { - var superScriptClass:AbstractScriptClass = cast(this.superClass, ScriptClass); + } else if (this.superClass != null && Std.isOfType(this.superClass, PolymodScriptClass)) { + var superScriptClass:PolymodAbstractScriptClass = cast(this.superClass, PolymodScriptClass); try { return superScriptClass.fieldWrite(name, value); } catch (e:Dynamic) { } diff --git a/polymod/hscript/PolymodClassDeclEx.hx b/polymod/hscript/PolymodClassDeclEx.hx index f3ebeaf1..e9c07292 100644 --- a/polymod/hscript/PolymodClassDeclEx.hx +++ b/polymod/hscript/PolymodClassDeclEx.hx @@ -2,7 +2,7 @@ package polymod.hscript; import hscript.Expr.ClassDecl; -typedef ClassDeclEx = {> ClassDecl, +typedef PolymodClassDeclEx = {> ClassDecl, @:optional var imports:Map>; @:optional var pkg:Array; } \ No newline at end of file diff --git a/polymod/hscript/PolymodInterpEx.hx b/polymod/hscript/PolymodInterpEx.hx index eee8150c..1e5255b2 100644 --- a/polymod/hscript/PolymodInterpEx.hx +++ b/polymod/hscript/PolymodInterpEx.hx @@ -1,7 +1,9 @@ package polymod.hscript; -#if hscript_ex + import hscript.AbstractScriptClass; import hscript.Interp; +import hscript.Expr; +import hscript.Tools; /** * Based on code by Ian Harrigan @@ -11,15 +13,19 @@ import hscript.Interp; @:access(polymod.hscript.PolymodAbstractScriptClass) class PolymodInterpEx extends Interp { var targetCls:Class; - public function new(targetCls:Class, proxy:AbstractScriptClass) { - super(proxy); + public function new(targetCls:Class, proxy:PolymodAbstractScriptClass) { + super(); + _proxy = proxy; + variables.set("Type", Type); + variables.set("Math", Math); + variables.set("Std", Std); this.targetCls = targetCls; } override function cnew(cl:String, args:Array):Dynamic { if (_scriptClassDescriptors.exists(cl)) { // OVERRIDE CHANGE: Create a PolymodScriptClass instead of a hscript.ScriptClass - var proxy:AbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(cl), args); + var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(cl), args); return proxy; } else if (_proxy != null) { @:privateAccess @@ -28,7 +34,7 @@ class PolymodInterpEx extends Interp { var packagedClass = _proxy._c.pkg.join(".") + "." + cl; if (_scriptClassDescriptors.exists(packagedClass)) { // OVERRIDE CHANGE: Create a PolymodScriptClass instead of a hscript.ScriptClass - var proxy:AbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(packagedClass), args); + var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(packagedClass), args); return proxy; } } @@ -38,7 +44,7 @@ class PolymodInterpEx extends Interp { var importedClass = _proxy._c.imports.get(cl).join("."); if (_scriptClassDescriptors.exists(importedClass)) { // OVERRIDE CHANGE: Create a PolymodScriptClass instead of a hscript.ScriptClass - var proxy:AbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(importedClass), args); + var proxy:PolymodAbstractScriptClass = new PolymodScriptClass(_scriptClassDescriptors.get(importedClass), args); return proxy; } @@ -57,36 +63,23 @@ class PolymodInterpEx extends Interp { */ override function fcall(o:Dynamic, f:String, args:Array):Dynamic { // OVERRIDE CHANGE: Custom logic to handle super calls to prevent infinite recursion - // trace('fcall: ${Type.typeof(o)}.${f}()'); - // trace('compare: ${targetCls}'); if (Std.isOfType(o, targetCls)) { // Force call super function. - //trace('Force call to __super_'); return super.fcall(o, '__super_${f}', args); } else if (Std.isOfType(o, PolymodScriptClass)) { _nextCallObject = null; - //trace('Fix call to self'); var proxy:PolymodScriptClass = cast(o, PolymodScriptClass); return proxy.callFunction(f, args); } - //trace('Call to other function'); return super.fcall(o, f, args); } - private var _proxy:AbstractScriptClass = null; - - public function new(proxy:AbstractScriptClass = null) { - super(); - _proxy = proxy; - variables.set("Type", Type); - variables.set("Math", Math); - variables.set("Std", Std); - } + private var _proxy:PolymodAbstractScriptClass = null; - private static var _scriptClassDescriptors:Map = new Map(); + private static var _scriptClassDescriptors:Map = new Map(); private static var _scriptClassOverrides:Map> = new Map>(); - private static function registerScriptClass(c:ClassDeclEx) { + private static function registerScriptClass(c:PolymodClassDeclEx) { var name = c.name; if (c.pkg != null) { name = c.pkg.join(".") + "." + name; @@ -102,12 +95,18 @@ class PolymodInterpEx extends Interp { var v = expr(e2); switch (Tools.expr(e1)) { case EIdent(id): - if (_proxy.superClass != null && Reflect.hasField(_proxy.superClass, id)) { - Reflect.setProperty(_proxy.superClass, id, v); - return v; - } + if (_proxy.superClass != null) { + if (Reflect.hasField(_proxy.superClass, id)) { + Reflect.setProperty(_proxy.superClass, id, v); + return v; + } else if (Type.getInstanceFields(Type.getClass(_proxy.superClass)).contains(id)){ + Reflect.setProperty(_proxy.superClass, id, v); + return v; + } + } case _: } + // trace('Call super assign'); return super.assign(e1, e2); } @@ -122,11 +121,10 @@ class PolymodInterpEx extends Interp { } override function get(o:Dynamic, f:String):Dynamic { - trace('get ${f}'); if (o == null) error(EInvalidAccess(f)); - if (Std.is(o, ScriptClass)) { - var proxy:AbstractScriptClass = cast(o, ScriptClass); + if (Std.isOfType(o, PolymodScriptClass)) { + var proxy:PolymodAbstractScriptClass = cast(o, PolymodScriptClass); if (proxy._interp.variables.exists(f)) { return proxy._interp.variables.get(f); } else if (proxy.superClass != null && Reflect.hasField(proxy.superClass, f)) { @@ -144,8 +142,8 @@ class PolymodInterpEx extends Interp { override function set(o:Dynamic, f:String, v:Dynamic):Dynamic { if (o == null) error(EInvalidAccess(f)); - if (Std.is(o, ScriptClass)) { - var proxy:ScriptClass = cast(o, ScriptClass); + if (Std.isOfType(o, PolymodScriptClass)) { + var proxy:PolymodScriptClass = cast(o, PolymodScriptClass); if (proxy._interp.variables.exists(f)) { proxy._interp.variables.set(f, v); } else if (proxy.superClass != null && Reflect.hasField(proxy.superClass, f)) { @@ -161,7 +159,6 @@ class PolymodInterpEx extends Interp { private var _nextCallObject:Dynamic = null; override function resolve(id:String):Dynamic { - trace('resolve ${id}'); _nextCallObject = null; if (id == "super" && _proxy != null) { if (_proxy.superClass == null) { @@ -175,12 +172,10 @@ class PolymodInterpEx extends Interp { var l = locals.get(id); if (l != null) { - trace('returning local'); return l.r; } var v = variables.get(id); if (v != null) { - trace('returning variable'); return v; } // OVERRIDE CHANGE: Allow access to modules for calling static functions. @@ -188,8 +183,6 @@ class PolymodInterpEx extends Interp { if (m != null) { var importedClass = m.join("."); - trace('returning imported module ${importedClass}'); - // TODO: Somehow allow accessing static fields of a ScriptClass without instantiating it. return Type.resolveClass(importedClass); @@ -197,25 +190,20 @@ class PolymodInterpEx extends Interp { if (_proxy != null && _proxy.findFunction(id) != null) { _nextCallObject = _proxy; - trace('returning resolve'); return _proxy.resolveField(id); } else if (_proxy != null && _proxy.superClass != null && (Reflect.hasField(_proxy.superClass, id) || Reflect.getProperty(_proxy.superClass, id) != null)) { _nextCallObject = _proxy.superClass; - trace('returning reflect'); return Reflect.getProperty(_proxy.superClass, id); } else if (_proxy != null) { try { var r = _proxy.resolveField(id); _nextCallObject = _proxy; - trace('returning proxy resolve'); return r; } catch (e:Dynamic) {} - trace('unknown variable and nonnull proxy'); error(EUnknownVariable(id)); } else { - trace('unknown variable and null proxy'); error(EUnknownVariable(id)); } return null; @@ -227,11 +215,11 @@ class PolymodInterpEx extends Interp { registerModule(decls); } - public function createScriptClassInstance(className:String, args:Array = null):AbstractScriptClass { + public function createScriptClassInstance(className:String, args:Array = null):PolymodAbstractScriptClass { if (args == null) { args = []; } - var r:AbstractScriptClass = cnew(className, args); + var r:PolymodAbstractScriptClass = cnew(className, args); return r; } @@ -248,7 +236,7 @@ class PolymodInterpEx extends Interp { case DClass(c): var extend = c.extend; if (extend != null) { - var superClassPath = new Printer().typeToString(extend); + var superClassPath = new hscript.Printer().typeToString(extend); if (imports.exists(superClassPath)) { switch (extend) { case CTPath(_, params): @@ -257,7 +245,7 @@ class PolymodInterpEx extends Interp { } } } - var classDecl:ClassDeclEx = { + var classDecl:PolymodClassDeclEx = { imports: imports, pkg: pkg, name: c.name, @@ -274,5 +262,4 @@ class PolymodInterpEx extends Interp { } } } -} -#end \ No newline at end of file +} \ No newline at end of file diff --git a/polymod/hscript/PolymodParserEx.hx b/polymod/hscript/PolymodParserEx.hx index 27a59527..00516d06 100644 --- a/polymod/hscript/PolymodParserEx.hx +++ b/polymod/hscript/PolymodParserEx.hx @@ -1,6 +1,8 @@ package polymod.hscript; -class PolymodParserEx extends hscript.Parser { +import hscript.Parser; + +class PolymodParserEx extends Parser { public override function parseModule( content : String, ?origin : String = "hscript" ) { var decls = super.parseModule(content, origin); return decls; diff --git a/polymod/hscript/PolymodScriptClass.hx b/polymod/hscript/PolymodScriptClass.hx index 7e7c04c4..6e58b44c 100644 --- a/polymod/hscript/PolymodScriptClass.hx +++ b/polymod/hscript/PolymodScriptClass.hx @@ -2,7 +2,11 @@ package polymod.hscript; using StringTools; -#if hscript_ex +import hscript.Expr.FieldDecl; +import hscript.Expr.FunctionDecl; +import hscript.Expr.VarDecl; +import hscript.Printer; + enum Param { Unused; } @@ -62,7 +66,7 @@ class PolymodScriptClass public static function clearScriptClasses() { @:privateAccess - InterpEx._scriptClassDescriptors.clear(); + PolymodInterpEx._scriptClassDescriptors.clear(); } /** @@ -127,7 +131,7 @@ class PolymodScriptClass } // Check if the superclass is a scripted class. - var classDescriptor = InterpEx.findScriptClassDescriptor(extendString); + var classDescriptor = PolymodInterpEx.findScriptClassDescriptor(extendString); if (classDescriptor != null) { @@ -182,10 +186,21 @@ class PolymodScriptClass trace('Could not determine target class for: ${c}'); } _interp = new PolymodInterpEx(targetClass, this); - super(c, args); + _c = c; + buildCaches(); + + var ctorField = findField("new"); + if (ctorField != null) { + callFunction("new", args); + if (superClass == null && _c.extend != null) { + @:privateAccess _interp.error(ECustom("super() not called")); + } + } else if (_c.extend != null) { + createSuperClass(args); + } } - private override function createSuperClass(args:Array = null) + private function createSuperClass(args:Array = null) { if (args == null) { @@ -198,7 +213,7 @@ class PolymodScriptClass extendString = _c.pkg.join(".") + "." + extendString; } - var classDescriptor = InterpEx.findScriptClassDescriptor(extendString); + var classDescriptor = PolymodInterpEx.findScriptClassDescriptor(extendString); if (classDescriptor != null) { var abstractSuperClass:PolymodAbstractScriptClass = new PolymodScriptClass(classDescriptor, args); @@ -227,7 +242,7 @@ class PolymodScriptClass } } - public override function callFunction(name:String, args:Array = null):Dynamic + public function callFunction(name:String, args:Array = null):Dynamic { // trace('Calling function ${name} on scripted class.'); var field = findField(name); @@ -282,9 +297,9 @@ class PolymodScriptClass var fixedName = '__super_${name}'; for (a in args) { - if (Std.is(a, ScriptClass)) + if (Std.is(a, PolymodScriptClass)) { - fixedArgs.push(cast(a, ScriptClass).superClass); + fixedArgs.push(cast(a, PolymodScriptClass).superClass); } else { @@ -296,8 +311,8 @@ class PolymodScriptClass return r; } - private var _c:ClassDeclEx; - private var _interp:InterpEx; + private var _c:PolymodClassDeclEx; + private var _interp:PolymodInterpEx; public var superClass:Dynamic = null; @@ -320,23 +335,23 @@ class PolymodScriptClass createSuperClass(args); } - private inline function callFunction0(name:String) { + private inline function callFunction0(name:String):Dynamic { return callFunction(name); } - private inline function callFunction1(name:String, arg0:Dynamic) { + private inline function callFunction1(name:String, arg0:Dynamic):Dynamic { return callFunction(name, [arg0]); } - private inline function callFunction2(name:String, arg0:Dynamic, arg1:Dynamic) { + private inline function callFunction2(name:String, arg0:Dynamic, arg1:Dynamic):Dynamic { return callFunction(name, [arg0, arg1]); } - private inline function callFunction3(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic) { + private inline function callFunction3(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic):Dynamic { return callFunction(name, [arg0, arg1, arg2]); } - private inline function callFunction4(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic) { + private inline function callFunction4(name:String, arg0:Dynamic, arg1:Dynamic, arg2:Dynamic, arg3:Dynamic):Dynamic { return callFunction(name, [arg0, arg1, arg2, arg3]); } @@ -416,4 +431,3 @@ class PolymodScriptClass } } } -#end diff --git a/samples/openfl_hscript_class/README.md b/samples/openfl_hscript_class/README.md new file mode 100644 index 00000000..55c3c567 --- /dev/null +++ b/samples/openfl_hscript_class/README.md @@ -0,0 +1,11 @@ +# samples/openfl_hscript_class + +This sample creates implements a simple Stage class, then creates a ScriptedStage scripted class type using Polymod. Polymod can then parse any scripted classes within the assets folder that extend Stage, and instantiate an instance of Stage which utilizes the behavior defined in the script. + +For example, the Basic stage creates a simple scene with a background, while the Advanced stage (from `mod6`) creates a scene with a background and a moving sprite. + +The Basic stage overrides the `create()` function of the stage to add additional behavior (i.e. rendering the background). + +The Advanced stage overrides `create()` to initialize the background and the moving sprite, but also overrides the `onUpdate()` and `onKeyPress()` functions to add additional behavior (moving the sprite, and changing the sprite's speed, respectively). + +This sample demonstrates how an application can easily instantiate and utilize classes defined in a script. This is useful for quick debugging without rebuilding an entire codebase, as well as implementation of robust modding support for your project. diff --git a/samples/openfl_hscript_class/assets/img/a.png b/samples/openfl_hscript_class/assets/img/a.png deleted file mode 100644 index 97284217..00000000 Binary files a/samples/openfl_hscript_class/assets/img/a.png and /dev/null differ diff --git a/samples/openfl_hscript_class/assets/img/b.png b/samples/openfl_hscript_class/assets/img/b.png deleted file mode 100644 index d00c34d2..00000000 Binary files a/samples/openfl_hscript_class/assets/img/b.png and /dev/null differ diff --git a/samples/openfl_hscript_class/assets/img/c.png b/samples/openfl_hscript_class/assets/img/c.png deleted file mode 100644 index 38400d98..00000000 Binary files a/samples/openfl_hscript_class/assets/img/c.png and /dev/null differ diff --git a/samples/openfl_hscript_class/mods/mod6/data/scripts/stage/advanced.hxc b/samples/openfl_hscript_class/mods/mod6/data/scripts/stage/advanced.hxc index 3303fb79..d96bde4a 100644 --- a/samples/openfl_hscript_class/mods/mod6/data/scripts/stage/advanced.hxc +++ b/samples/openfl_hscript_class/mods/mod6/data/scripts/stage/advanced.hxc @@ -8,15 +8,57 @@ class AdvancedStage extends Stage { stageName = "Advanced Stage"; } + var carSprite:Bitmap; + public override function create():Void { super.create(); - var landscapeBg = new Bitmap(Assets.getBitmapData('img/stage/landscape.png')); + var landscapeBg = new Bitmap(Assets.getBitmapData('img/stage/street.png')); landscapeBg.x = 0; landscapeBg.y = 0; landscapeBg.width = 480; landscapeBg.height = 360; + carSprite = new Bitmap(Assets.getBitmapData('img/stage/car.png')); + carSprite.x = 50; + carSprite.y = 240; + carSprite.width = 100; + carSprite.height = 100; + this.addChild(landscapeBg); + this.addChild(carSprite); + } + + var carSpeed:Float = 1.0; + public override function onUpdate():Void { + super.onUpdate(); + + carSprite.x += carSpeed; + + if (carSprite.x > 330 || carSprite.x < 50) { + trace('Bounce!'); + carSpeed *= -1; + } + // trace('car: '+carSpeed+' : '+carSprite.x); + } + + public override function onKeyDown(event:KeyboardEvent):Void { + switch(event.keyCode) { + case 38: // Up + trace('Key pressed: Up'); + carSpeed *= 1.5; + case 40: // Down + trace('Key pressed: Down'); + carSpeed *= 0.75; + default: + // Do nothing. + trace('Key pressed: ${event.keyCode}'); + } + if (Math.abs(carSpeed) < 0.1) { + carSpeed = 0.1; + } + if (Math.abs(carSpeed) > 10) { + carSpeed = 10; + } } } \ No newline at end of file diff --git a/samples/openfl_hscript_class/mods/mod6/img/stage/car.png b/samples/openfl_hscript_class/mods/mod6/img/stage/car.png new file mode 100644 index 00000000..c0c719d3 Binary files /dev/null and b/samples/openfl_hscript_class/mods/mod6/img/stage/car.png differ diff --git a/samples/openfl_hscript_class/mods/mod6/img/stage/street.png b/samples/openfl_hscript_class/mods/mod6/img/stage/street.png new file mode 100644 index 00000000..b46783b7 Binary files /dev/null and b/samples/openfl_hscript_class/mods/mod6/img/stage/street.png differ diff --git a/samples/openfl_hscript_class/mods/mod7/LICENSE.txt b/samples/openfl_hscript_class/mods/mod7/LICENSE.txt new file mode 100644 index 00000000..88c23727 --- /dev/null +++ b/samples/openfl_hscript_class/mods/mod7/LICENSE.txt @@ -0,0 +1,5 @@ +Copyright 2018 Lars A. Doucet + +Code and script files (*.hscript/*.hs) are licensed by CODE_LICENSE.TXT +All other files are licensed by ASSET_LICENSE.TXT + \ No newline at end of file diff --git a/samples/openfl_hscript_class/mods/mod7/_polymod_icon.png b/samples/openfl_hscript_class/mods/mod7/_polymod_icon.png new file mode 100644 index 00000000..5f964e16 Binary files /dev/null and b/samples/openfl_hscript_class/mods/mod7/_polymod_icon.png differ diff --git a/samples/openfl_hscript_class/mods/mod7/_polymod_meta.json b/samples/openfl_hscript_class/mods/mod7/_polymod_meta.json new file mode 100644 index 00000000..f8b10602 --- /dev/null +++ b/samples/openfl_hscript_class/mods/mod7/_polymod_meta.json @@ -0,0 +1,8 @@ +{ + "title":"Car Skin", + "description":"This mod reskins the car from the Advanced stage", + "author":"Eric", + "api_version":"0.1.0", + "mod_version":"1.0.0-alpha", + "license":"CC BY 4.0,MIT" +} \ No newline at end of file diff --git a/samples/openfl_hscript_class/mods/mod7/img/stage/car.png b/samples/openfl_hscript_class/mods/mod7/img/stage/car.png new file mode 100644 index 00000000..999ebb32 Binary files /dev/null and b/samples/openfl_hscript_class/mods/mod7/img/stage/car.png differ diff --git a/samples/openfl_hscript_class/src/Demo.hx b/samples/openfl_hscript_class/src/Demo.hx index a3180261..99271ff6 100644 --- a/samples/openfl_hscript_class/src/Demo.hx +++ b/samples/openfl_hscript_class/src/Demo.hx @@ -1,3 +1,4 @@ +import openfl.events.KeyboardEvent; import stage.Stage; import stage.StubStage; import stage.ScriptedStage; @@ -11,7 +12,7 @@ import openfl.utils.AssetType; class Demo extends Sprite { private var widgets:Array = []; - private var stageButtons:Array = []; + private var stageButtons:Array = []; private var callback:Array->Void; private var stuff:Array = []; private var limeToggle:CheapButton; @@ -20,7 +21,7 @@ class Demo extends Sprite public static var usingOpenFL(default, null):Bool = true; - var dirty:Bool = false; + var dirty:Bool = false; public function new(callback:Array->Void) { @@ -28,7 +29,7 @@ class Demo extends Sprite this.callback = callback; - initStageData(); + initStageData(); setStage(); makeButtons(); @@ -36,8 +37,18 @@ class Demo extends Sprite drawStage(); drawImages(); drawText(); + } + public function addListeners() { + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + } + + function onKeyDown(e:KeyboardEvent) { + if (curStage != null) + curStage.onKeyDown(e); + } + public function destroy() { for (w in widgets) @@ -55,18 +66,20 @@ class Demo extends Sprite removeChild(cast thing); } stuff.splice(0, stuff.length); - drawStage(); + drawStage(); drawImages(); drawText(); - initStageData(); - drawStageButtons(); + initStageData(); + drawStageButtons(); + + addListeners(); } private function makeButtons() { addModWidgets(); - drawStageButtons(); + drawStageButtons(); updateWidgets(); addToggle(); } @@ -105,69 +118,81 @@ class Demo extends Sprite } } - var stageData:Array = null; - function initStageData():Void { - stageData = []; - var stageClassNames:Array = ScriptedStage.listScriptClasses(); - - stageData.push(new StubStage()); - for (stageClassName in stageClassNames) { - var defaultStageId:String = 'STAGE_${Std.random(256)}'; - var stage:ScriptedStage = ScriptedStage.init(stageClassName, defaultStageId); - if (stage != null) { - stageData.push(stage); - } - } - } + var stageData:Array = null; - function listStages():Array { - return [for (stage in stageData) stage.stageId ]; - } + function initStageData():Void + { + stageData = []; + var stageClassNames:Array = ScriptedStage.listScriptClasses(); + + stageData.push(new StubStage()); + for (stageClassName in stageClassNames) + { + var defaultStageId:String = 'STAGE_${Std.random(256)}'; + var stage:ScriptedStage = ScriptedStage.init(stageClassName, defaultStageId); + if (stage != null) + { + stageData.push(stage); + } + } + } + + function listStages():Array + { + return [for (stage in stageData) stage.stageId]; + } function setStage(?stageId:String = 'stub'):Void { - if (curStage != null) { - removeChild(curStage); - curStage = null; - } - for (stage in stageData) { - if (stage.stageId == stageId) { - curStage = stage; - } - } + if (curStage != null) + { + removeChild(curStage); + curStage = null; + } + for (stage in stageData) + { + if (stage.stageId == stageId) + { + curStage = stage; + } + } } private function drawStage() { curStage.x = 5; curStage.y = 5; - curStage.create(); addChild(curStage); + curStage.create(); } - private function drawStageButtons() { - stageButtons = []; - for (stage in stageData) { - trace('STAGE BUTTON : ${stage.stageName} : ${stage.stageId} : ${stage}'); - var button = new CheapButton(stage.stageName, function() { - trace('Pressed button: ${stage.stageName}'); - setStage(stage.stageId); - refresh(); - }); - stageButtons.push(button); - stuff.push(button); - } - - var xx = 10 + 72 + 10; - for (button in stageButtons) { - button.x = xx; - button.y = 550; - // Make sure the buttons get cleaned up when reloading mods. - stuff.push(button); - addChild(button); - xx += 72 + 10; - } - } + private function drawStageButtons() + { + stageButtons = []; + for (stage in stageData) + { + trace('STAGE BUTTON : ${stage.stageName} : ${stage.stageId} : ${stage}'); + var button = new CheapButton(stage.stageName, function() + { + trace('Pressed button: ${stage.stageName}'); + setStage(stage.stageId); + refresh(); + }); + stageButtons.push(button); + stuff.push(button); + } + + var xx = 10 + 72 + 10; + for (button in stageButtons) + { + button.x = xx; + button.y = 550; + // Make sure the buttons get cleaned up when reloading mods. + stuff.push(button); + addChild(button); + xx += 72 + 10; + } + } private function addToggle() { diff --git a/samples/openfl_hscript_class/src/Main.hx b/samples/openfl_hscript_class/src/Main.hx index f1c5a15e..770ed034 100644 --- a/samples/openfl_hscript_class/src/Main.hx +++ b/samples/openfl_hscript_class/src/Main.hx @@ -12,17 +12,18 @@ class Main extends Sprite public function new() { super(); - // Initialize Polymod but load no mods - loadMods([]); + // Initialize Polymod but load no mods + loadMods([]); loadDemo(); - // test(); + // test(); } - function test() { - var defaultStageId:String = 'STAGE_${Std.random(256)}'; - var stage:ScriptedStage = ScriptedStage.init('BasicStage', defaultStageId); - stage.create(); - } + function test() + { + var defaultStageId:String = 'STAGE_${Std.random(256)}'; + var stage:ScriptedStage = ScriptedStage.init('BasicStage', defaultStageId); + stage.create(); + } private function loadDemo() { @@ -51,7 +52,7 @@ class Main extends Sprite ignoredFiles: Polymod.getDefaultIgnoreList(), framework: framework, assetPrefix: '', - useScriptedClasses: true, + useScriptedClasses: true, }); } diff --git a/samples/openfl_hscript_class/src/stage/ScriptedStage.hx b/samples/openfl_hscript_class/src/stage/ScriptedStage.hx index 0a8d0a91..2e4b82bd 100644 --- a/samples/openfl_hscript_class/src/stage/ScriptedStage.hx +++ b/samples/openfl_hscript_class/src/stage/ScriptedStage.hx @@ -3,18 +3,18 @@ package stage; import polymod.hscript.HScriptable; @:hscriptClass -class ScriptedStage extends Stage implements HScriptable { +class ScriptedStage extends Stage implements HScriptable +{ } - /* -``` -class BasicStage extends Stage { - public override function create():Void { - super.create(); - // Do custom stuff... - } -} -``` + ``` + class BasicStage extends Stage { + public override function create():Void { + super.create(); + // Do custom stuff... + } + } + ``` -var currentStage:Stage = ScriptedStage.init('BasicStage', []); -*/ \ No newline at end of file + var currentStage:Stage = ScriptedStage.init('BasicStage', []); + */ diff --git a/samples/openfl_hscript_class/src/stage/Stage.hx b/samples/openfl_hscript_class/src/stage/Stage.hx index af2fdabd..e5dcc5ce 100644 --- a/samples/openfl_hscript_class/src/stage/Stage.hx +++ b/samples/openfl_hscript_class/src/stage/Stage.hx @@ -1,30 +1,44 @@ package stage; +import openfl.events.KeyboardEvent; +import openfl.events.Event; import openfl.display.Sprite; import openfl.display.Bitmap; import openfl.display.BitmapData; class Stage extends Sprite { - var test:Stage; + var test:Stage; public var stageId:String = "UNKNOWN"; public var stageName:String = "UNKNOWN"; public function new(id:String) { - super(); - trace('Initializing Stage: $id'); - // No-op constructor. - this.stageId = id; + super(); + trace('Initializing Stage: $id'); + this.stageId = id; + this.stageName = id; + + addEventListener(Event.ENTER_FRAME, onUpdate); + } + + static final MAGENTA = 0xFFFF00FF; + + public function create():Void + { + // Clear all children so we don't add a sprite twice. + removeChildren(); + + var baseBg = new Bitmap(new BitmapData(480, 360, false, MAGENTA)); + addChild(baseBg); } - static final MAGENTA = 0xFFFF00FF; - public function create() { - // Clear all children so we don't add a sprite twice. - removeChildren(); + public function onUpdate(event:Event):Void + { + //trace('onUpdate'); + } - var baseBg = new Bitmap(new BitmapData(480, 360, false, MAGENTA)); - addChild(baseBg); + public function onKeyDown(event:KeyboardEvent):Void { } } diff --git a/samples/openfl_hscript_class/src/stage/StubStage.hx b/samples/openfl_hscript_class/src/stage/StubStage.hx index fab58cc7..8cc0c3d6 100644 --- a/samples/openfl_hscript_class/src/stage/StubStage.hx +++ b/samples/openfl_hscript_class/src/stage/StubStage.hx @@ -1,14 +1,15 @@ package stage; -import openfl.Assets; +class StubStage extends Stage +{ + public function new() + { + super('stub'); + this.stageName = "Stub Stage"; + } -class StubStage extends Stage { - public function new() { - super('stub'); - this.stageName = "Stub Stage"; - } - - public override function create() { - super.create(); - } -} \ No newline at end of file + public override function create() + { + super.create(); + } +}