You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
source/Prebuild.hx:15: Building...
source/funkin/util/macro/RegistryMacro.hx:46: ENTRY: Song
source/funkin/util/macro/RegistryMacro.hx:24: REGISTRY: SongRegistry
source/funkin/util/macro/RegistryMacro.hx:46: ENTRY: PlayableCharacter
source/funkin/util/macro/RegistryMacro.hx:24: REGISTRY: PlayerRegistry
source/funkin/util/macro/RegistryMacro.hx:24: REGISTRY: AlbumRegistry
source/funkin/util/macro/RegistryMacro.hx:46: ENTRY: Album
source/funkin/util/macro/RegistryMacro.hx:46: ENTRY: Conversation
source/funkin/util/macro/RegistryMacro.hx:24: REGISTRY: ConversationRegistry
source/funkin/util/macro/RegistryMacro.hx:46: ENTRY: NoteStyle
source/funkin/util/macro/RegistryMacro.hx:24: REGISTRY: NoteStyleRegistry
ERROR source/funkin/ui/freeplay/Album.hx:99: characters 26-34
99 | return AlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id));
| ^^^^^^^^
| Class<funkin.data.freeplay.album.AlbumRegistry> has no field instance
Album
packagefunkin.ui.freeplay;
importfunkin.data.freeplay.album.AlbumData;
importfunkin.data.freeplay.album.AlbumRegistry;
importfunkin.data.animation.AnimationData;
importfunkin.data.IRegistryEntry;
importflixel.graphics.FlxGraphic;
/** * A class representing the data for an album as displayed in Freeplay.*/
@:build(funkin.util.macro.RegistryMacro.buildEntry())
classAlbumimplementsIRegistryEntry<AlbumData>
{
/** * The internal ID for this album.*/publicfinalid:String;
/** * The full data for an album.*/publicfinal_data:AlbumData;
publicfunctionnew(id:String)
{
this.id=id;
this._data=_fetchData(id);
if (_data==null)
{
throw'Could not parse album data for id: $id';
}
}
/** * Return the name of the album. * @*/publicfunctiongetAlbumName():String
{
return_data.name;
}
/** * Return the artists of the album. * @return The list of artists*/publicfunctiongetAlbumArtists():Array<String>
{
return_data.artists;
}
/** * Get the asset key for the album art. * @return The asset key*/publicfunctiongetAlbumArtAssetKey():String
{
return_data.albumArtAsset;
}
/** * Get the album art as a graphic, ready to apply to a sprite. * @return The built graphic*/publicfunctiongetAlbumArtGraphic():FlxGraphic
{
returnFlxG.bitmap.add(Paths.image(getAlbumArtAssetKey()));
}
/** * Get the asset key for the album title.*/publicfunctiongetAlbumTitleAssetKey():String
{
return_data.albumTitleAsset;
}
publicfunctionhasAlbumTitleAnimations()
{
return_data.albumTitleAnimations.length>0;
}
publicfunctiongetAlbumTitleAnimations():Array<AnimationData>
{
return_data.albumTitleAnimations;
}
publicfunctiontoString():String
{
return'Album($id)';
}
publicfunctiondestroy():Void {}
staticfunction_fetchData(id:String):Null<AlbumData>
{
returnAlbumRegistry.instance.parseEntryDataWithMigration(id, AlbumRegistry.instance.fetchEntryVersion(id));
}
}
AlbumRegistry
packagefunkin.data.freeplay.album;
importfunkin.ui.freeplay.Album;
importfunkin.data.freeplay.album.AlbumData;
importfunkin.ui.freeplay.ScriptedAlbum;
@:build(funkin.util.macro.RegistryMacro.buildRegistry())
classAlbumRegistryextendsBaseRegistry<Album, AlbumData>
{
/** * The current version string for the album data format. * Handle breaking changes by incrementing this value * and adding migration to the `migrateAlbumData()` function.*/publicstaticfinalALBUM_DATA_VERSION:thx.semver.Version='1.0.0';
publicstaticfinalALBUM_DATA_VERSION_RULE:thx.semver.VersionRule='1.0.x';
publicstaticfinalinstance:AlbumRegistry=newAlbumRegistry();
publicfunctionnew()
{
super('ALBUM', 'ui/freeplay/albums', ALBUM_DATA_VERSION_RULE);
}
/** * Read, parse, and validate the JSON data and produce the corresponding data object. * @paramid The ID of the entry to load. * @return The parsed data object.*/publicfunctionparseEntryData(id:String):Null<AlbumData>
{
// JsonParser does not take type parameters,// otherwise this function would be in BaseRegistry.varparser:json2object.JsonParser<AlbumData> =newjson2object.JsonParser<AlbumData>();
parser.ignoreUnknownVariables=false;
switch (loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
returnnull;
}
if (parser.errors.length>0)
{
printErrors(parser.errors, id);
returnnull;
}
returnparser.value;
}
/** * Parse and validate the JSON data and produce the corresponding data object. * * NOTE: Must be implemented on the implementation class. * @paramcontents The JSON as a string. * @paramfileName An optional file name for error reporting. * @return The parsed data object.*/publicfunctionparseEntryDataRaw(contents:String, ?fileName:String):Null<AlbumData>
{
varparser:json2object.JsonParser<AlbumData> =newjson2object.JsonParser<AlbumData>();
parser.ignoreUnknownVariables=false;
parser.fromJson(contents, fileName);
if (parser.errors.length>0)
{
printErrors(parser.errors, fileName);
returnnull;
}
returnparser.value;
}
functioncreateScriptedEntry(clsName:String):Album
{
returnScriptedAlbum.init(clsName, 'unknown');
}
functiongetScriptedClassNames():Array<String>
{
returnScriptedAlbum.listScriptClasses();
}
}
RegistryMacro
packagefunkin.util.macro;
importhaxe.macro.Context;
importhaxe.macro.Expr;
importhaxe.macro.Type;
usinghaxe.macro.ExprTools;
usinghaxe.macro.TypeTools;
usingStringTools;
classRegistryMacro
{
publicstaticmacrofunctionbuildRegistry():Array<Field>
{
varfields=Context.getBuildFields();
varcls=Context.getLocalClass().get();
if (!cls.name.endsWith('Registry'))
{
throw'${cls.module}.${cls.name} needs to end with "Registry"';
}
trace('REGISTRY: ${cls.name}');
vartypeParams=getTypeParams(cls);
varentryCls=typeParams.entryCls;
varjsonCls=typeParams.jsonCls;
varscriptedEntryCls=getScriptedEntryClass(entryCls);
fields=fields.concat(buildRegistryVariables(cls));
fields=fields.concat(buildRegistryMethods(cls));
buildEntryImpl(entryCls, cls);
buildRegistryImpl(cls, entryCls, scriptedEntryCls, jsonCls);
returnfields;
}
publicstaticmacrofunctionbuildEntry():Array<Field>
{
varfields=Context.getBuildFields();
varcls=Context.getLocalClass().get();
trace('ENTRY: ${cls.name}');
varentryData=getEntryData(cls);
// since the registries also use a build macro// the fields aren't callable unless we first get// the class type of the registrymakeFieldsCallable(cls);
fields=fields.concat(buildEntryVariables(cls, entryData));
fields=fields.concat(buildEntryMethods(cls));
returnfields;
}
#if macro
staticfunctionmakeFieldsCallable(cls:ClassType)
{
// TODO: lets not have this if statement// like what the hell is wrong with this// doing this if statement fixes the order// for the song build macrosif (cls.name=='Song')
{
MacroUtil.getClassTypeFromExpr(macrofunkin.data.song.SongRegistry);
return;
}
varregistries:Array<String> = [];
for (localImportinContext.getLocalImports())
{
varnames= [];
for (pathinlocalImport.path)
{
names.push(path.name);
}
varfullName=names.join('.');
if (fullName.endsWith('Registry'))
{
registries.push(fullName);
}
}
for (registryinregistries)
{
MacroUtil.getClassTypeFromExpr(Context.parse(registry, Context.currentPos()));
}
}
staticfunctionfieldAlreadyExists(name:String):Bool
{
for (fieldinContext.getBuildFields())
{
if (field.name==name&&!((field.access?? []).contains(Access.AAbstract)))
{
returntrue;
}
}
functionfieldAlreadyExistsSuper(name:String, superClass:Null<ClassType>)
{
if (superClass==null)
{
returnfalse;
}
for (fieldinsuperClass.fields.get())
{
if (field.name==name&&!field.isAbstract)
{
returntrue;
}
}
// recursively check superclassesreturnfieldAlreadyExistsSuper(name, superClass.superClass?.t.get());
}
returnfieldAlreadyExistsSuper(name, Context.getLocalClass().get().superClass?.t.get());
}
staticfunctiongetTypeParams(cls:ClassType):RegistryTypeParamsNew
{
switch (cls.superClass.t.get().kind)
{
caseKGenericInstance(_, params):
vartypeParams:Array<Dynamic> = [];
for (paraminparams)
{
switch (param)
{
caseTInst(t, _):
typeParams.push(t.get());
caseTType(t, _):
typeParams.push(t.get());
default:
throw'Not a class';
}
}
return {entryCls: typeParams[0], jsonCls: typeParams[1]};
default:
throw'Not in the correct format';
}
}
staticfunctiongetScriptedEntryClass(entryCls:ClassType):ClassType
{
varscriptedEntryClsName=entryCls.pack.join('.') +'.Scripted'+entryCls.name;
switch (Context.getType(scriptedEntryClsName))
{
caseType.TInst(t, _):
returnt.get();
default:
throw'Not A Class (${scriptedEntryClsName})';
};
}
staticfunctiongetEntryData(cls:ClassType):Dynamic// DefType or ClassType
{
switch (cls.interfaces[0].params[0])
{
caseType.TInst(t, _):
returnt.get();
caseType.TType(t, _):
returnt.get();
default:
throw'Entry Data is not a class or typedef';
}
}
staticfunctionbuildRegistryVariables(cls:ClassType):Array<Field>
{
varclsType:ComplexType=Context.getType('${cls.module}.${cls.name}').toComplexType();
varnewInstance:String='new ${cls.module}.${cls.name}()';
return (macroclassTempClass
{
staticvar_instance:Null<$clsType>;
publicstaticvarinstance(get, never):$clsType;
staticfunctionget_instance():$clsType
{
if (_instance==null)
{
_instance= ${Context.parse(newInstance, Context.currentPos())};
}
return_instance;
}
}).fields.filter((field) ->return!fieldAlreadyExists(field.name));
}
staticfunctionbuildRegistryMethods(cls:ClassType):Array<Field>
{
varimpl:String='funkin.macro.impl._${cls.name}_Impl';
return (macroclassTempClass
{
functiongetScriptedClassNames()
{
return ${Context.parse(impl, Context.currentPos())}.getScriptedClassNames(this);
}
functioncreateScriptedEntry(clsName:String)
{
return ${Context.parse(impl, Context.currentPos())}.createScriptedEntry(this, clsName);
}
publicfunctionparseEntryData(id:String)
{
return ${Context.parse(impl, Context.currentPos())}.parseEntryData(this, id);
}
publicfunctionparseEntryDataRaw(contents:String, ?fileName:String)
{
return ${Context.parse(impl, Context.currentPos())}.parseEntryDataRaw(this, contents, fileName);
}
}).fields.filter((field) ->return!fieldAlreadyExists(field.name));
}
staticfunctionbuildEntryVariables(cls:ClassType, entryData:Dynamic):Array<Field>
{
varentryDataType:ComplexType=Context.getType('${entryData.module}.${entryData.name}').toComplexType();
return (macroclassTempClass
{
publicfinalid:String;
publicfinal_data:Null<$entryDataType>;
}).fields.filter((field) ->return!fieldAlreadyExists(field.name));
}
staticfunctionbuildEntryMethods(cls:ClassType):Array<Field>
{
varimpl:String='funkin.macro.impl._${cls.name}_Impl';
return (macroclassTempClass
{
publicfunction_fetchData(id:String)
{
return ${Context.parse(impl, Context.currentPos())}._fetchData(this, id);
}
publicfunctiontoString()
{
return ${Context.parse(impl, Context.currentPos())}.toString(this);
}
publicfunctiondestroy()
{
${Context.parse(impl, Context.currentPos())}.destroy(this);
}
}).fields.filter((field) ->!fieldAlreadyExists(field.name));
}
staticfunctionbuildRegistryImpl(cls:ClassType, entryCls:ClassType, scriptedEntryCls:ClassType, jsonCls:Dynamic):Void
{
varclsType:ComplexType=Context.getType('${cls.module}.${cls.name}').toComplexType();
vargetScriptedClassName:String='${scriptedEntryCls.module}.${scriptedEntryCls.name}';
varcreateScriptedEntry:String='${scriptedEntryCls.module}.${scriptedEntryCls.name}.init(clsName, "unknown")';
varnewJsonParser:String='new json2object.JsonParser<${jsonCls.module}.${jsonCls.name}>()';
Context.defineType(
{
pos: Context.currentPos(),
pack: ['funkin', 'macro', 'impl'],
name: '_${cls.name}_Impl',
kind: TypeDefKind.TDClass(null, [], false, false, false),
fields: (macroclassTempClass
{
publicstaticinlinefunctiongetScriptedClassNames(me:$clsType)
{
return ${Context.parse(getScriptedClassName, Context.currentPos())}.listScriptClasses();
}
publicstaticinlinefunctioncreateScriptedEntry(me:$clsType, clsName:String)
{
return ${Context.parse(createScriptedEntry, Context.currentPos())};
}
publicstaticinlinefunctionparseEntryData(me:$clsType, id:String)
{
varparser= ${Context.parse(newJsonParser, Context.currentPos())};
parser.ignoreUnknownVariables=false;
@:privateAccessswitch (me.loadEntryFile(id))
{
case {fileName: fileName, contents: contents}:
parser.fromJson(contents, fileName);
default:
returnnull;
}
if (parser.errors.length>0)
{
@:privateAccessme.printErrors(parser.errors, id);
returnnull;
}
returnparser.value;
}
publicstaticinlinefunctionparseEntryDataRaw(me:$clsType, contents:String, ?fileName:String)
{
varparser= ${Context.parse(newJsonParser, Context.currentPos())};
parser.ignoreUnknownVariables=false;
parser.fromJson(contents, fileName);
if (parser.errors.length>0)
{
@:privateAccessme.printErrors(parser.errors, fileName);
returnnull;
}
returnparser.value;
}
}).fields
});
}
staticfunctionbuildEntryImpl(cls:ClassType, registryCls:ClassType):Void
{
varclsType:ComplexType=Context.getType('${cls.module}.${cls.name}').toComplexType();
varregistry:String='${registryCls.module}.${registryCls.name}';
Context.defineType(
{
pos: Context.currentPos(),
pack: ['funkin', 'macro', 'impl'],
name: '_${cls.name}_Impl',
kind: TypeDefKind.TDClass(null, [], false, false, false),
fields: (macroclassTempClass
{
publicstaticinlinefunction_fetchData(me:$clsType, id:String)
{
return $
{
Context.parse(registry, Context.currentPos())
}.instance.parseEntryDataWithMigration(id, ${Context.parse(registry, Context.currentPos())}.instance.fetchEntryVersion(id));
}
publicstaticinlinefunctiontoString(me:$clsType)
{
return $v{cls.name} +'('+me.id+')';
}
publicstaticinlinefunctiondestroy(me:$clsType) {}
}).fields
});
}
#end
}
#if macro
typedefRegistryTypeParamsNew=
{
varentryCls:ClassType;
varjsonCls:Dynamic; // DefType or ClassType
}
#end
Information
Haxe: 4.3.4
Platform: Windows
Target: cpp
the RegistryMacro creates fields for an Entry or Registry if not yet defined
this works fine if the order of the build macro is Entry -> Registry (atleast i assume)
as you can see in the error when the Album and AlbumRegistry gets built, it builds the AlbumRegistry first and then Album, and i assume this is the problem
so my question is if there is a way to make the build order not matter,
or if there is a way to determine the build order
The text was updated successfully, but these errors were encountered:
Reference Repository
https://github.com/lemz1/Funkin/tree/feature/registry-marco-broken
Error and Relevant Classes
Error
Album
AlbumRegistry
RegistryMacro
Information
Haxe: 4.3.4
Platform: Windows
Target: cpp
the
RegistryMacro
creates fields for an Entry or Registry if not yet definedthis works fine if the order of the build macro is
Entry -> Registry
(atleast i assume)as you can see in the error when the
Album
andAlbumRegistry
gets built, it builds theAlbumRegistry
first and thenAlbum
, and i assume this is the problemso my question is if there is a way to make the build order not matter,
or if there is a way to determine the build order
The text was updated successfully, but these errors were encountered: