#Registries Overhaul
1 messages · Page 2 of 1
You shouldn’t even need to have to store a reference of biomes. In fact you really shouldn’t. Otherwise the Holder would have to be rebound and it becomes complicated. If someone needs an instance of a biome for whatever reason you grab it with its resource key from a registry access.
Reference the key of the object, not the object itself
Yeah, in that instance it is fine that DH does not work for datapack regs
those should be retrieved from the reg once available
(if you even ever need those for anything)
Vanilla does it often. Look at how rabbits determine their variant on spawn based on biome
or foxes
DR objects use holders to autowire themselves to each other
well the rabbit doesn't need a Holder<Biome>, it could just use Biome
perhaps
tell you the truth, that example was pretty terrible
because rabbits only need to know what biome it is (aka the ID of it). They don’t need to know anything about the biome
Perhaps rain was a better example
Rain doesn’t happen in hotter biomes
or snow in colder
POINT IS. Datapack objects you just need a resource key. No more, no less
no that doesn't make any sense to do given this is purely for builtin registries and worldgen registries have their own non-infinite lifetime
no, sometimes you need both
placedfeatures need the configuredfeature itself
and yeah worldgen registries never get to the root reg
even if they did, like
let's say you make a static deferredholder for a biome that somehow gets the biome value when the worldgen registries are created
yes but configured features aren’t built in are they?
if you're playing in singleplayer, does it hold the client biome or the server biome
(they're not the same biome)
uh
I do have to ask though, what is the process for making a new registry going to look like and how are we going to make it more similar to the vanilla system?
are all dpregs synced?
they are
then probably the server

I mean, but then you compare it to the biome or whatever on the client and suddenly they're not equivalent when they ought to be. Gets dicey fast. I'd just say no deferred holders for datapack registries
also the biomes on the client don't even have the same data as the biomes on the server
but yeah that
Yeah I believe that would be the correct approach
DH == "core" regs only
so, registryobjects but holders
So, anything owned by the root registry
especially since /reload can be invoked and the new datapack data could delete entries and then weird stuff happens with DH
I mean yeah, the initial goal here was just to make RO implement Holder<T>
but then I was like 
Datapack registries don't change with /reload, but yeah, otherwise yeah
that part makes me screm
Yeah. Datapack registry entries aren't reloaded with /reload
the fact that DPR doesn't /reload and no mechanism exist to makes them do so is a bit spaghetti
That's only half true. The order only matters for new worlds if forge feels like it
For existing worlds, order is based on order mods originally were in with new ones appended
o-O am I going nuts? Thought I’ve had that happen before
Other datapack stuff does but not registries
You may have for mods that use reload listeners for datapack data
but not for things that are "proper" datapack regs
perhaps. Alright thanks for clearing that one up
If forge switched to a compound datapacks for all mods like they do for resource packs, it could always matter and save a lot of pack maker headache though
Still a bit curious about this though. Can we somehow make new registry creation use Holders? #1131130393144336385 message
Or otherwise be more vanilla like?
The only downside is you cannot reorder mod datapacks with commands, which no one wants to do anyways
Not quite sure how vanilla does it actually
I should make a difference thread about that probably
new registry creation is just new MappedRegistry for vanilla
I would implore keeping it that way
and then registration to the root reg™️
NewRegistryEvent can stay as an early "register your new regs"
but ideally™️ reg creation is
public static final Registry<Spaghetti> MY_REGISTRY = new MappedRegistry<>(...);
syncing and saving (both forge features) will need to either be done via extension methods which raise flags (and become locked at registration time) or via ctor params (annoying for updating vanilla)
Yeah. Heck, could we use the deferred holder system to build creation and registration in to the same thing? Unless the ctor already does that
DefReg iirc already has that capacity (to make the registry for the underlying type)
but the return type of that method isn't Registry
Hmm. But I mean, if you're using def reg you're not really using the vanilla system anymore?
But yeah, I guess that makes sense
I mean in general I would never advise using a pure vanilla system unless you have absolute classloading control (i.e. you declare the registry and all entries in clinit for a particular type)
I currently have opted for package private extension methods that you have to use a builder to touch. Making registries as early as possible makes more sense after the conversation I read thru. But people will definitely still do it wrong (as in not register it). At least we can make it error though.
I mean that's fine
public static final Registry<T> REG = Registry.builder().blah().saved().build();
How would that error exactly?
It's easy
it doesn't need to error if its only accessible via the builder, lol
Like, we should still allow creation of registries unlinked to the root registry. Heck, vanilla uses that for world gen. I'd say it would error when someone tries to register to it
new RegistryBuilder<>(KEY), check if KEY is registered to root registry later
Oh, you mean with the builder. Yeah, fair
I figured even that might not be necessary as we could just watch for people trying to register stuff to registries that don't exist (not registered )during the registry events
better safe than sorry
But yeah, using the builder is clean
you don't need that many sanity checks, DH will just explode if the registry isn't registered
Especially if you expect most people to use that
and it will say that explicitly "no registry exists"
(is this all package private in vanilla normally?)
I don't think many things in vanilla are package private
What is the access modifier on making a new registry in vanilla?
well, the helpers are private, but you can just copy them
But the actual constructor is public then?
so in literal terms, public™️
Then yeah, we don't need any special checks in the builder, if DH would error anyways and if they can always just use the constructor straight away
In general I always disliked the excessive amount of defensive programming
Yeah, I'm with you there
Doesn't ever protect as much as it claims to, and makes it way harder for the one guy who actually has a reason to dig under it
Unless its in netcode
also, slight detail.. has anyone checked the thread safety of holder creation
which I'm going to assume isn't safe
normal holder creation? no, not a chance, lol
getOrCreateHolder is a write operation on the vanilla registries and vanilla registries are not threadsafe
could frozen registries be made threadsafe?
I guess if all write operations are forbidden then they already would be
ah no, reloading tags is a write operation on... something
They're thread safe during common setup and after registry reloads though, right?
maybe, unless somebody's cheesing things into the registries post-registration
(which I suspect is something that both of us do)
okay, tag reloading is an atomic operation on the registry's tag map, but mutates all holders iteratively
Nope! I do it via a mixin to forge registries to do it during the proper registration. Stuff like content tweaker does do stuff afterwards? But I think they do it on freeze, before common setup. Not entirely sure
I unfreeze the dimension registry during game runtime
https://github.com/Commoble/infiniverse/blob/main/src/main/java/commoble/infiniverse/internal/DimensionManager.java#L156
Heh. Well, yeah, fair enough
I'm excited to hopefully get registry callbacks as a way around that rather ugly mixin I currently use
half the reason I originally wanted this to be an official forge feature was so people wouldn't assume the dimension registry was immutable (because immersive portals was caching it for some reason)
hrm
looks to me like people should always use DeferredHolder then
Within a parallelizable context I'd say yeah, it's probably safer
I'm a little bit worried that DH's excessive bind() calls could result in a CME if they are reading the holder map from the reg during holder creation
I haven't looked at your implementation at all
if we get registry callbacks we can register all DH's as callbacks and then that won't be an issue
technically™️ the registry step is singlethreaded so it shouldn't be an issue™️
not really
even though people can statically create registries
those registries won't be registered immediately
therefore there won't always be a registry to lookup to register to
all registries will be available after NewRegistryEvent, which DH's can listen to and bind
that's exactly what ModifyRegistryEvent is for
PoC is pain
I'm thinking phase 2 would be a nice idea
do a role reversal and have forge registry be a wrapper on registry<?>
I hope that's not used with the other meaning
stupid to mention that
Phase 2 will be great, just lets not repeat Thor: Dark World okey?
https://github.com/neoforged/NeoForge/pull/35 is ready for merge unless anyone else has any reservations
I will try to take a look at it in the next couple days, but I think you need to rebase as the jar compat check is failing (as things have been added that aren’t in your pr due to older base)
screm
compat check should probably run a local rebase first
or, well, wait actually
doesn't the compat check failure on any method removal, not just api-breaking changes?
wondering if its picking up removal of the package-private methods or smth
thing is, I technically fixed it doing that
oh well, rebase is clean, so that took 3 seconds
CI at my work runs a rebase before doing any pipeline things, but I'm not sure how difficult that is for our infra to handle
I thought so too, but like why else did it fail and claim those things?
Looks good to me. The only thing I think is not ideal is the constant pointless registry lookup in value() (https://github.com/neoforged/NeoForge/pull/35/files#diff-23a6a6d322a2b82410f4bf00fdc3fb70ec82e2956980dbf6c896a06b36a0b342R90). I think a throwOnMissingReg flag passed to bind() would be better
hm, yeah
No it's set to api mode
oh, neat, didn't realize that was a thing
I think I recall it complaining about removing a non-api method at some point
it should respect api status internal now I think
bleh, I've realized I want to write something but it depends on defholder existing
just write it against maven local and then wait a day or two to commit 
@hoary umbra why isn't there a factory method that simply takes a resource key?
uh
idk™️
because you can't make a strong resource key on your own, its a huge pita
and the expectation is that your DH is strongly-typed to your object type
the docs probably shouldn't be using <br> but we haven't standardized that yet
and also I don't think there should be spaces between the doc and the method or class start
is <br> illegal now 
what does this mean?
nah
making the resourcekey correctly requires a raw cast
can't you use a dangling generic?
I've tried everything I can think of, the issue is in the signature of ResourceKey.create
it only returns a type of the registry type
registry keys are ResourceKey<Registry<T>>, not ResourceKey<? extends Registry<? super T>>
they're ResourceKey<? extends Registry<T>>
yeah it's certified awful
r u sure
that doesn't sound right
anyhow we can leave the javadoc formatting up to #1131782966989828156
I thought vanilla used ? extends Registry<T> for everything
it does
i think maybe they used to have that wildcard mess in, like, 1.18.2 when i first looked at it
obviously the declarations aren't
I find it unlikely that we will be autoformatting everything esp javadocs but yeah
meh nothing in this system is obvious
the declarations could be if Mojang wanted
The javadocs in there are autoformatted by my eclipse profile
well the decls are decompiled, so who knows
true I guess
honestly
i'd be half tempted to make DeferredHolder take two types
the registry type and a more specific type
yeah but that achieves nothing, lol
it doesn't fix the unchecked cast and it just makes all declarations longer
it lets DeferredHolder<Block, MyBlock> implement Holder<Block> so it can be part of a set of Holder<Block>s
it already can be, wew type erasure
i still don't understand the unchecked cast
I left the compiler error in the pr
or why the type can't simply be ResourceKey<Registry<T>>
It could be, but that doesn't fix it
? extends Registry<? super T>> is just the biggest "umbrella"
how doesn't it fix it
voila fixed. only the ? super is problematic, even. and I don't understand why it has to be there.
now get me a ? extends Registry<MyBlock>, lol
you've just moved the cast location to outside the method
but Registries.BLOCK's type is ResourceKey<Registry<Block>>
yes, so now create only makes DH<Block>
public DeferredHolder(ResourceKey<? super T> resourceKey) {
// ...
}
public static <T, U extends T> DeferredHolder<U> create(ResourceKey<Registry<T>> registryKey, ResourceLocation valueName)
{
return new DeferredHolder<U>(ResourceKey.create(registryKey, valueName));
}
bam
Hm, I suppose we can bypass the type on the key like that
like ultimately the type of the key is block
this does mean you have an unchecked cast in the value method, but that's the method that actually has the potential of returning the wrong type and will have a checkcast in its caller
like, ultimately the question to consider is
what actually happens if someone creates a deferredholder for a type that doesn't match the actual type placed in the registry
no matter how the unchecked casts are arranged, at the end of the day the return value of value() is caller-checked
though an unchecked cast is a little less ugly than a raw type at least
No just make it a factory like it should be
I see u stealing what RegistryObject already does
top 10 illegal generic techniques
We got a summary on the end-goal for this?
Like what an average user’s registry process would look like
Well, I was writing some stuff up in placebo that will be cleaner when DH is present, but I was thinking just like this https://i.imgur.com/vIWbqfG.png
currently I have to do some fairly ugly reflection to make this possible, lol
pretty much
this change makes RO a Holder<T> so it can be used in Holder places
RO is currently really bulky
its just a huge pile of overengineered spaghetti
I see
this is basically the first step in "delete ForgeRegistry"
I was expecting something actually useful from a registry overhaul in neoforge
as it also makes all registries vanilla-backed
well there will be™️
but not yet
because it requires changing so many internals
one step at a time
we are still at non-breaking-change phase
Is there a reason it needs to be not yet?
non-breaking-change phase™️
Then defer the change for breaking so you can do it properly?
This seems rushed for the sake of it
why would I defer this part until later?
Idk I’m just coming in off the periphery
Ultimately this is mostly an internals thing
If this is 100% non breaking then I guess its ok from a dev perspective
the registry system is a horrible unmaintainable mess
If theres any amount of change required from the dev side for this its an outright disappointment from my perspective
probably not significant differences to the typical end-user
Not significant isnt none..
I mean the class ForgeRegistries will vanish
So this specific pr is typically 100% binary compatible with current mods (ignoring edge cases)
?
yes
Ok cool
this is outlined in https://github.com/neoforged/NeoForge/issues/37
I mean as long as you don’t count “deprecated for removal” warnings in your ide as change required… there shouldn’t be any… I mean unless you are reflecting into registry object internals… but then that is kind of on you
Without looking at the internals what consequences will this change have for most everyday uses beside not referencing forge registries anymore?
you'd be renaming RegistryObject to DeferredHolder or something to that effect
And that's it?
- continue using deferredregisters to register stuff
- DeferredHolder instead of RegistryObject
- query vanilla registries when actually reading registries (I already do this anyway)
using registry keys more when creating deferred registers too, i think
since deferredregister.create or whatever has like three overloads
So from an outsider prespecrive it's just a name change
RegistryObject still remains, but it's deprecated and it's only a light wrapper over DeferredHolder
it'll get yeeted come breaking changes time 
Right so it's not a breaking chsnge for first phase:TM:
what if RegistryObject is an abstract class that DeferredHolder extends
What would be the point of that? The idea behind DeferredHolder is to keep the lazy init of RegistryObject while being a Holder, in part to make multiplatform stuff simpler
for backwards compatibility, i had it in my head that if we do that then DeferredRegister.register can return an actual DeferredHolder rather than a wrapper
actually is there a need for there to be two classes at any time? why couldn't we just make RegistryObject implement Holder, and wait for breaking to rename
That would be a signature change and therefore not binary compatible. DeferredRegister.register must return RegistryObject until the next breaking phase. RegistryObject is also not a wrapper
Ease of discovery by virtue of deprecation
re: https://github.com/neoforged/NeoForge/pull/35#pullrequestreview-1554878082
@winged iris / @quaint pagoda does vanilla do registry sync at all? I thought vanilla did not perform sync of any kind with regard to registry content
I am pretty sure the answer is no
but you would have to ask pup how that comment came about
it doesn't (need to)
I haven't had a chance to look through the code and realized that maybe that is something we have to worry about
why do you think vanilla does? In my eyes we would have already removed the forge feature if vanilla did this
why would vanilla do it in the first place 
to be nice :>
vanilla? nice? funny joke
yeah I'd be very surprised if vanilla does any registry syncing in the static registries
it doesn't make sense for vanilla to sync registries when they're static init'd smh. useless packets
even the numeric ids should be consistent in vanilla
numeric ids are the thing you need to sync/persist
https://github.com/neoforged/NeoForge/pull/35/files/bfc02f7771326c0b3d4286252a0a68f8c1e38277#r1279514088 @winged iris I don't see how this replaces the feature but also I don't think anyone was using the feature. @hoary umbra can you explain how it works now for optional registries
right but I mean in vanilla they're probably consistent on both sides without needing a sync
yes
All DH's support optional registries
just don't call .get()
because, obviously, the item is not present since the registry is not present
and my comment was just asking to make sure whether or not the binding would cause issues
I thought it wouldn't but wanted to check
no bc the event doesn't fire for registries that don't exist
given the fact that the registry event shouldn't fire if the registry doesn't exist
I know
I just wanted confirmation is why I asked
I mean technically it could, I guess
there's no sanity checks in the signature to make sure the registry is present 
the ctor should check at least one of the regs is present
and the names used are based on this weird patched-in MappedRegistry.getKnownRegistries()
that's so that it is in a consistent order and one that follows vanilla's init order
that sounds bad
how are you supposed to ensure your entries registered even if you don't want to query them
why...?
well
isPresent 
like by default I think we should error if an entry isn't registered when the user expected it to be registered
which is already what happens
if they're my own entries, for vanilla's registries or my own registries, I can reasonably assume they're registered without checking isPresent
if my own entries didn't register then something went horribly wrong and it should crash very fast
it's not always going to crash fast though
I remember figuring out and writing the impl of MappedRegistry.getKnownRegistries with lex at some point because there were various issues (namely with configured and placed feature ordering)
those're datapack registries now though 
I'm curious if the deferred register not being registered throws an error if the registry object doesn't detect itself as registered (right now without this PR)
not all entries are needed to be queried with get() but are still necessary to be registered
e.g. command argument providers
so? The point still stands that some registries interact with each other and firing based on vanilla ordering makes it so that you can at least consistently reference stuff
I'm never gonna need to query that entry but it still has to be registered
also the way vanilla holders work is that if a holder was created for a registry but the value was never registered/bound, the registry throws on freeze
and those were the two most common ones when we wrote that bit so that it fires in that order
if the registry exists then the element will be registered, unless you don't register your DR, in which case it just fails anyway
the point of the createOptional things was by default things explicitly error if they don't get registered
all createOptional did was error check if the registry exists, you can do that yourself
rip yeah I guess that's all it did
I guess it's not worth keeping lol https://i.imgur.com/ZEAJMCz.png
huh, cofh core is using it https://i.imgur.com/U5PbB3i.png
nvm it's just static wrapper methods that are never actually used
see these people are using it actually correctly https://github.com/TwelveIterationMods/Waystones/blob/d0b9024a8fac9c6236d88c673d305d51bfd38ecd/forge/src/main/java/net/blay09/mods/waystones/compat/RepurposedStructuresIntegration.java#L13
this is a lot easier than declaring an optional dependency on repurposed structures to ensure their mod constructor runs first so that you can do a ModList check from your constructor to optionally register the Deferred Register
actually I guess you don't have to sort your mod constructor to know if a mod is loaded
so meh it's only like a one liner difference to change the DR registration, theoretically
it's a pretty niche case anyways because the custom registry has to not use custom mod class types for it to be useful at all anyways without requiring a ModList#isLoaded check before loading the class and getting massive classloading errors
anyways all that to say I guess it makes sense to get rid of as it's not doing much at all
Yeah if it was doing something else that was potentially more useful I could see it being helpful
but its just a very obscure "does registry exist" check
marking that comment as resolved
maybe we should link back to this convo as evidence
done™️
fairly certain it is fine, not marking it as approved yet (may not until tomorrow or something idk) as I want to test it against say mek to do an extra level of testing/validation that it doesn't cause any compile or runtime breaking changes
yeah, not 100% certain on the impact of everything having a vanilla registry
pretty sure it's harmless
"what's the worst that could happen?"
well that part I don't know, especially given my custom registries 4/5 of them have vanilla registries already
@hoary umbra <br> is fine in brand new javadocs but you shouldn't really mix it into javadocs that have an existing standard being used until we get an autoformatter or defined standard somewhere.
e.g. https://github.com/neoforged/NeoForge/pull/35/files#diff-4b3668674d37f99def6bc1719fad4898bf978e0dd0037ba59b2d60860ce3dc75R57
that's just causing 2 blank lines for no reason
I have thoughts on this. this is a bad javadoc in my eyes.
Instead, we should be pointing people to ForgeRegistries$Keys class.
Additionally, we should deprecate all keys that point to vanilla registries and link back to the vanilla key, and have people use that, as they will be deleted.
so, most of the keys in ForgeRegistries$Keys should be deprecated and disappear soon
I can also move these to github, probably should
I did that
I think messing with the key deprecations is out of scope for now, I was going to do another one which shuffles the deprecation fields from the vanilla stuff to the forge stuff
Ok well at least point to ForgeRegistries.Keys instead of suggesting getRegistryKey
yeah that's fine
Not sure why the dislike for <br> and <p> in the same thing, they have different purposes
I use br to split sentences to their own lines, and p to split ideas into their own paragraphs
though any cases of <br> with an immediate <p> following are just me being stupid
^
people have a go at me for doing the same thing lol
br and p format differently, so I use them differently
I wasn't aware of that. Different how?
the thing is most of our codebase already isn't using br
most of our codebase is using whatever the writer decided was good enough, lol
mm fair most of the time I find p and br to be equivalent and stick to p for larger separation but that's new info to me
i.e. this one
the first three are <br> but the space between time. and Example is <p>
if I just drop the <br>'s it reads like this which is just harder to parse imo https://i.imgur.com/v91HhAM.png
difference of opinions then
I think adding a line break after every sentence is unnecessary
the first three sentences are disjoint but the not disjoint enough that they warrant a new paragraph
it is unnecessary if the sentences form a coherent thought
let's poke @lofty citrus for a qualified opinion
yes that sounds good
he can do a formal writeup which we can all vote upon if he wants :p
In absolute terms I use
<br> for disjoint sentences that are part of the same thought
<p> for disjoint thoughts
I can trust a person's judgment and still have my own opinions
if we "just trust" people then nothing is actually decided upon democratically smh
just trust me bro™️
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
you mean <bro>
we're good with this DR modid -> namespace change right? The ctor is private so nobody could be extending the class
mfw find-replace
Also, anyone else have any thoughts on providing a "super-DR" which is preloaded with helpers for the vanilla registries?
that way you don't need to make multiple DR objects, just one
That sounds like a potential maintenance nightmare. Either way out of out of scope of this PR
yeah not for now
but it is a little bit cleaner (imo) to have one object that you call .item(() -> x) .block(() -> x) etc on instead of one per registry
Throwing my own opinion in on the whole br/p tag thing: I think it's rare to see individual sentences that are disjoint enough to warrant new lines but not a new paragraph, but br still has uses as a formatting mark when a visual break is necessary
But like, this? That looks bad. Imagine the window was half the width. You'd have a lot of partial little lines
As someone who works on a low resolution screen, single line paragraphs are hell and should be avoided like the plague
Because they turn into one-and-an-eigth line paragraphs instead
So if the sentences aren't disjoint enough for a paragraph break, I'd string em together
That way you don't get lots of little short fragments that don't wrap right as you're trying to read on a small screen (or heck, read javadocs on mobile!)
I don't think that box gets any smaller (I'm pretty sure it's fixed-width by default)
Keep in mind that people read javadocs in lots of different ways
They're just HTML when it comes down to it
If it would be a bad idea on a webpage, it's probably a bad idea in javadocs (to a reasonable degree, of course)
(at least in IntelliJ it scales to your screen width to a degree, if your screen is small. Or maybe that's a setting I turned on. So it's definitely not "fixed width" and should not be treated as such)
eclipses is fixed width for sure, bc its this little tiny box no matter how I resize the window
Yeah, but eclipse is not what everyone uses
I mean yeah, but neither is IDEA, so what's the point of this convo lol
as long as the doc is readable this is just nitpicking
My point is that with line breaks like that, it is not very readable in several fairly common places
Such as IntelliJ, or on the web
yeah but that's an unnecessary restriction
Or, heck, for anyone who doesn't have a screen wide enough to show that whole line at once
No i mean for bincompat with the rename
if the class was (currently) extensible I couldn't rename the field
alright, things updated again
someone should fix the compat check to auto-rebase before checking 
meh
It should automatically work
it tries to find the fork point to bin compat check with
re: javadoc
<br>/hard line breaks should be avoided when possible. <p> for paragraph separation (none of this <br><br> nonsense). don't be afraid to have paragraphs with multiple sentences; they're normal. the first paragraph of a javadoc is special anyway, because (a) it's the first thing the reader readers in logical order, and (b) the first sentence of the first paragraph is taken as its summary (except when overriden by {@summary}, but that rarely if ever happens)
out of curiosity is there any reason javadoc can't use something like markdown, outside of the {@things}?
cuz this isn't kotlin?
because javadoc was invented in 1996
I mean, it's just the tool displaying it that needs to support it, right?
yes and that includes all IDEs
it's not really feasible for us to go to markdown at all
all two ide's, lol
but idk how complicated ide plugins are to write
apparently it was attempted https://github.com/openjdk/jdk/pull/11701
I'm just gonna say now we are not gonna write custom IDE plugins that we have to maintain just to use markdown...
and that would install all users wanting to read our jdocs to use those plugins
Yeah there's not been any more noise on the jdk issue for now
markdown isn't particularly nice for things like @link
I largely prefer javadoc to whatever python does... you can't really @param with markdown
someoen should create a "markdoc" that's like markdown but with additional syntax for tagging params, return types and such
I mean as a tool you can run on a source folder and it outputs documentation
/*#
@Summary
Wheee `code`
@Param arg1
The first argument. See @[](dev.wheee.TheClass) for details.
@Return
Returns `nothing`.
#*/
``` 
@hoary umbra this was caused by u from upstream merges I think
---
Index: src/main/resources/forge.exc
===================================================================
diff --git a/src/main/resources/forge.exc b/src/main/resources/forge.exc
--- a/src/main/resources/forge.exc (revision d80f945e2b9ebfc07908e7cf573000e514ad795c)
+++ b/src/main/resources/forge.exc (revision 1f323abe4a129bba3640345b9a7690b727834584)
@@ -87,6 +87,8 @@
net/minecraft/server/network/ServerStatusPacketListenerImpl.<init>(Lnet/minecraft/network/protocol/status/ServerStatus;Lnet/minecraft/network/Connection;Ljava/lang/String;)V=|p_272864_,p_273586_,statusCache
net/minecraft/server/packs/metadata/pack/PackMetadataSection.<init>(Lnet/minecraft/network/chat/Component;ILjava/util/Map;)V=|p_10371_,p_10372_,packTypeVersions
net/minecraft/server/packs/repository/Pack$Info.<init>(Lnet/minecraft/network/chat/Component;IILnet/minecraft/world/flag/FeatureFlagSet;Z)V=|f_244592_,dataFormat,resourceFormat,f_244041_,hidden
+net/minecraft/tags/TagFile.<init>(Ljava/util/List;ZLjava/util/List;)V=|f_215959_,f_215960_,remove
+net/minecraft/tags/TagLoader$EntryWithSource.<init>(Lnet/minecraft/tags/TagEntry;Ljava/lang/String;Z)V=|f_216042_,f_216043_,remove
net/minecraft/world/entity/Entity.changeDimension(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraftforge/common/util/ITeleporter;)Lnet/minecraft/world/entity/Entity;=|p_20118_,teleporter
net/minecraft/world/entity/Entity.playCombinationStepSounds(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)V=|p_277472_,p_277630_,primaryPos,secondaryPos
net/minecraft/world/entity/Entity.playMuffledStepSound(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;)V=|p_283110_,pos
it causes patch artifacts which I found while working on the overhaul
scrim
hopefully this will be solved by the new PR check going forward? But I thought that would have already caught this
when did this get merged...
had to be part of the huge upstream pr, right?
yes
anyways a status update about the overhaul (at least, my draft of it):
I got everything compiling but haven't run the game yet
currently all forge registries are now just Registry<xxx>
and Registry now extends INewForgeRegistry with extra methods that either vanilla is missing cuz bad api or like, aliases
smh, INewForgeRegistry and not INeoForgeRegistry
hmmm honestly good point
I thought we pinned XExtension earlier
I was probably gonna move it to just IForgeRegistry after I delete the original
so it should just be IRegistryExtension™️
that can be discussed later 
I still have to actually make syncing and serializing work
and also reverting to frozen when you leave a world
the system is a fucking mess if you couldn't tell
hm
you know, thinking about it
why does it need to revert to frozen
it has to reconfigure when you load up a world
and it always does that, so... 
shrug it can be removed later
get a working product that does what the old system does is my current priority then api design and review can be done
not to mention if your deferredholder shit is merged soon then I have an entire other thing to worry about rebasing on top of...
god you did a lot of changes to that pr
did not notice
ah
only the last two commits on the bottom are "new"
oop
I edited message
Unless we feel like debating javadoc semantics anymore we're probably good :p

The namespace will be the one passed to the constructor.
imo this is still too generic and should specify the namespace is the one used when the DR was constructed
screm
the autoformat line adjust all params to start at the same column is a bit annoying but that's probably a eclipseism
what does constructor indicate, the other constructor?
in my eyes this could easily be confusing as you are registering a supplier to a constructor, i.e. factory
I'm pretty sure anyone with half a braincell does not need that javadoc at all, lol
using {@linkplain #namespace the provided namespace}.
honestly this makes a lot more sense to use
autoformatter probably did that
yeah turn it off 
namespace is protected
there should be a public getter
therefore the jdoc is always relevant
I am quoting from jdoc for createTagKey
and that format should likely be copied to #register
linking to the exact namespace used
* @param path The path of the new entry. The namespace will be {@linkplain #namespace the provided namespace}.
that work?
I hate having "namespace" twice in the sentence, idk brain no like™️
up to you if you want to make it a getter so you can query the namespace the defreg was made with without having to subclass it
I don't know how useful being able to query the namespace is soo
Probably not very, tbh
I still want to contribute a "super defreg" which is one to many instead of one to one (in terms of defreg to registry)
meh
at that point just use registerevent
that would be a lot of duplication of the same registry key over and over if its once per field
nah builtin helpers for the vanilla classes
eww
I do not like the idea of hardcoding a bunch of vanilla classes
well it has all of the vanilla helpers + public <P, T extends P> RegistryObject<T> custom(String path, ResourceKey<Registry<P>> registry, Supplier<T> factory)
that's just more maintenance
it means we have to add or remove every time vanilla does
its not like vanilla is going to remove their registries, lol
I still don't like the maintenance burden and it's just method spaghet/explosion for not much reason
it would basically make defreg or whatever custom class less resilient across mc versions
defreg is so generic that it mostly is consistent
now we would have to consider renaming things as vanilla renames them and things not working as well across mc version boundaries
eh it should be fine with DH being a thing now
this is only terrible rn due to how I have to reflect into RO
why do u have to
package private spaghetti 🙃
there's no other way to bind an RO and I didn't want to reimplement it
are you making update reference public or something?
I guess the alternative is I make this class delegate to a fuckton of DR's
but that's also bleh
btw ping me with the new commit in your pr when it is done and otherwise it's up to pup / any other team mebers
how do you plan to do it with DH
ah yes
(DH is also non-final so I can just do that too)
hopefully that doesn't become a problem
cuz bind eager vs bind on call is semantically different
its still binds eager for ones from DR
yeah I guess so
I would say you could try to limit the random whitespace changes but I don't personally care that much given I've also done the same before
is the namespace exposed as a getter?
I mean once bound its just a != null
no it's a protected field
hmmmm
this is my current changes for my draft of the overhaul of registries https://github.com/neoforged/NeoForge/compare/1.20.x...SizableShrimp:NeoForge:1.20.x-overhaul-registries-cherrypicked
101 dalmations files changed
why? vanilla already reconfigures the tags when you load up a new world, you can just inject the ID save loading hook somewhere near the initial updateTags call instead of adding another layer of complexity
thats what I said 
it depends on if I preserved the dummying system or not
blocks used to get dummied into air
that means you would have extra entries depending on what world you are in
and if you dont revert to frozen or clear then they would never go away
that thing is worthless, lol
I think it is gone in my thing tho
right now only the aliases would pollute the registries if I didn't revert to frozen
aliases don't get ids
that's not what I said
they're polluting the registry
in my eyes it is the safest bet to have it be like normal when not in a world
its a huge overhead though
with how I've designed it not really
in the old system sure
as reverting to frozen and loading from disk were separate
I'm trying to unify those codepaths
and it's I think maybe 2-3 patches to do?
it's actually 2 method calls and neither change patches cuz they are in forge classes and there's a layer of indirection
I have to fix all of these and it's annoying af https://i.imgur.com/Qu9JLCc.png
esp. cuz I have brain fog cuz I like let this sit for a while
last doc change pushed
time for shrimp's never-ending nitpicks
nit-stabby
if we're doing DeferredHolder<MyBlock>, how does that affect e.g. Holder#is(TagKey<T>)?
@hoary umbra update: trying to run the registry overhaul has led me to find multiple infinite stackoverflow loops
hooray
It defers to the actual holder that comes from the registry once it exists
screm
at that point why implement Holder
if for most operations you'll want to cast to the base
the generics are always the problem 
we could do DeferredHolder<Block, MyBlock> and solve a lot of generic problems (this is an option i suggested before, but worth bringing up again with the method parameter issues), at the cost of longer declarations
that gets tricky while trying to maintain signature compat with RO
I'm pretty sure we can override get [and value] to return the more specific type
because RO has to then be DH<T, T>
Yes, you can
This generics issue is nothing new, RO#getHolder() already blows up with exactly the same problem
not new perhaps but more visible
Ah, yeah, that issue
generics are all fake anyways
you should just actually test it
but also I think double the generics is a horrible solution
Nah, that wouldn't be breaking, probably
it's for damn sure be source breaking, unclear if it'd be binary breaking
Hmm. What else do you do besides double generics?
but anywy
we can do one thing now and another thing in the next phase when RO is gone gone
Source breaking yes. Binary almost certainly no
horrible idea
ah yes, that works
Oh god. Type safety would be nice
good plan™️
type safety on what, its a tag key, lol
like resource key, the generic is entirely made up™️
RO spits out exactly the same thing
yeah I figured
Hmm. I think long term double generics may be the only way to fix this
if we want to avoid rawtypes, yeah
Meanwhile I'm just typing my ROs to the basic type (i.e. Block)
type safety from someone trying to check their item for membership in a block tag
That just tossed the ball. The type safety there is made up but still useful to avoid passing your thing intended as an item tag to a block
BLOCKS.register("test_block", () -> new AirBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));
this would infer AirBlock twice
like, it won't crash, it'll just silently accept the argument and not work
you can yeet type safety but have runtime safety
tag keys contain the associated registry ID
yes that's safe
but it doesn't stop you from passing in the wrong thing
safely
this is the string capability ids argument all over again
its just mojang fault for not making it ? super T
Nah, take the second inference from the type of the DeferredRegister
i don't blame them, generic wildcards are hell
Then you'll infer AirBlock and Block
yeah, <U extends T> DeferredHolder<T, U> register(string, supplier<U>)
@quaint pagoda what think, dual generic or rawtype overloads
we can go the BDFL route, no overlords
Given that the type info has to be somewhere you'll have to use one of the two
though rawtype overloads are never going to work right with public Stream<TagKey<T>> tags()
the rawtypes are so cursed, we might as well just have register return a Supplier that secretly implements Holder
Keep in mind that if you choose raw type overloads, you'll be unable to pass it to anywhere where vanilla expects a normal Holder<Block> unless you have a cast method or something
it's generic hell, we should be able to just fix vanilla's methods manually
no, that's fine oddly
pls no
We'll screw over multiloader dev if we do that
no more messing with vanilla signatures
how would it screw multiloader
You can't pass a Holder<AirBlock> to a Holder<Block> argument
Cause suddenly I need to think about forge patches anywhere vanilla has holders with tightly bounded arguments
ok it is a problem, but it's just a problem the raw overloads don't solve, not one that they cause.
that's where i was confused
use ? super T where?
in the .is() methods
you cannot
??
Shrimp means in a patch on vanilla
oh, not just in the DH override?
Nonetheless public Stream<TagKey<T>> tags() is still a problem
not really
Which... Please can we not change signatures of vanilla methods? It always causes pain
that one cannot be ? super T
tagkey<myblock> wouldn't be a mega problem
Yes it would
Cause you couldn't compare it to anything
you cannot ask a Holder<Block> if it is(TagKey<MyBlock>)
what if we just... waited until the next breaking period to add DeferredHolder, and toss out RegistryObject when we did
this is how
you can if you patch it to use ? super T
i asked this before! i don't get the point of phase 1
MyBlock is not ? super Block, lol
That's not the issue. The issue is that people would expect to make a DeferredHolder<MyBlock> but vanilla has Holder<Block>'s
we could just tell them no
My personal preference is double generics
DeferredHolder<Block> is what you get
yeah honestly RO's signature is a non-issue, that thing is spaghetti™️
doubel generics should be fine
cast it yourself if you want
It's easy, it's typesafe, and it works
it's so much more verbose for so little gain
I mean, is it?
it's either that or make DeferredHolder not implement Holder :p
Item is four letters
Anything else requires vanilla patches that don't actually solve the issue
just add a unsafe cast method that lets people get whatever type of holder they want unsafely
If we do that, let's make the actual holder type (Block) the default and have people unsafe cast to their mod block types
it is either that or not implement holder, unfortunately
I mean I can just strip the direct holder impl and add .holder() which generic downcasts to whatever
no good solutions, java generic just bad™️
probably a better idea imo. but it also wouldn't really fix .is(TagKey)
the java generics aren't bad mojang just didn't design their system right
It would cause you'd get an actual holder at the end
well then DH gets a native is<TagKey<? super T>> and holder() for other things
you'd do MY_THING.<Block>holder().is(BlockTags.STONE)
which is ugly
and not intuitive
a cursed suggestion would be adding isForge methods thru an interface extension and isForge(TagKey<?>) etc.
They can't have both the is and the TagKey stream stuff working without a strict bound on T. One would be made flexible with super, and the other with extends
Still doesn't fix the tag stream one
Question if you add an intermediary class that then rawtypes the generics on the stream and the is tagkey does Java let you then make deferredholder extend that and use whatever generics you please?
That Block would be inferred
So you'd be fine
it wouldn't, inferrence is serial
i mean, no - the "actual" type on the TagKeys is always the registry type [Block, Item etc]
so still super
its also very annoying that public <R super T> Holder<R> holder() { is illegal, because it does mean that you have to downcast
maybe we just make .get() downcast, idk
Yeah that's my point there. You'd have to return a super in one and accept a super in another. Returning a super isn't great inference, so generally you accept an extends instead
but then supplier's signature isn't correct
The only thing that makes everything work™️ is dual generic
Not always? I'm fairly certain Java can solve that type algebra for this example
Personally, dual generics aren't a huge issue. It's, like, one find and replace or whatever to add them
I'd say go for it, since it is the least intrusive solution that still has all the various features we'd like
oh god that's cursed but it might work
ha no
what happens if I implement rawtype Holder 
If you've made your tag keys also raw types you're not doing well
Okay, so, besides the slight extra verbosity, what are the actual issues with double generics?
what 
it would be stream<TagKey>
Alternatively can we just add extra methods with slightly different names in DH that act as bouncers for the proper type? And if someone is using a DH they use those as needed but if they pass their DH as a holder still (maybe?)
no it wouldnt
No shadows
When you kill class level rawtypes it nukes everything
They can't pass it as a Holder<Block> any more to any method expecting that
that sounds like what I suggested
why tf does it do that
Groovy lets you get around this. Just rewrite forge in groovy /s
screm to generics
cuz backwards compat
no /s, just do it
But seriously:
Okay, so, besides the slight extra verbosity, what are the actual issues with double generics?
There are none its just the verbosity, its probably the right call
Idk I am fine with double generics, some of my registry object wrappers have between two and five generics
no.
I really wish Java let you have var fields with initializers right about now
I think they mean IntermediaryClass<T> extends Holder with no T
what if you had DeferredHolder deprecate is and add an isTag
i thought the point was to extend Holder<T> but magically make is(TagKey<? super T>) work
people are just suggesting the same things now
Well that isn’t what I meant but is maybe worth trying as well, but I think it nukes all generics even higher up
well, DH would put back whatever generics we want
alas, no
you cannot override a raw method with a method with a proper generic param, period, regardless of what happened higher up
Doesn't matter. You still have a Holder<MyBlock> that can't be passed to anything expecting a Holder<Block>
Turning the question the other way round, what's the problem with making people type their DHs to the registry base type (i.e. Block, Item, etc.)?
carpal tunnel
I frequently have methods on my classes that I don't want to call via ((MyClass) DH.get()).blah()
oh you mean like
If they want to get their block back out of it. But yeah, that's also an option
hmm
Technically we could add a getTyped but that would be an unchecked cast
honestly this would work. but you would have to duplicate a lot of methods
what if we made it opt-in
its already an unchecked cast
DeferredHolder<Block> block = register("block", () -> new ReallySpecificBlockClass())
no sugarcoating it, its always an unchecked cast, even now
that's no good because you need the actual type of the block a lot
Yes
Could just have a <B extends T> B getAndCast() method
DeferredHolder has a single type argument, but it implements an interface with two type arguments
<Z extends T> Z getUnchecked() { return (Z) get(); }
and has a method that casts to this interface
Tbh double generic is likely the way to go (even though then the supplier bit might not be the generic type the person wants but
)
Yeah, I like this
getUnchecked is going to have inference problems
Unless we do double generic
I can't just call getUnchecked().myMethod()
Eh, store it on a local variable
double generic is fine, you'll only ever have to type the whole type in your static init fields
Or just do double generics
Definitely
also honestly
I have never run into issues doing it this way and I have all my stuff typed to base type
Item, <- 6 chars
imo if you go dual generics route then you should not deprecate registryobject for removal and just remove the holder stuff from it
screms return (T) this.holder.get(); (it did this anyway, but now I have to look at it)
and if your supplier returns the wrong type you can cast there to avoid having to write type params on register
people who want deferredholder -> DeferredRegister#registerHolder, have to hold 2 generics
people who dont need holder -> DeferredRegister#register, just like normal, everything is the same
Why wouldn't it be deprecated for removal still?
BlockEntityType would like to have a word with you
RO has to extend DH, there is no "remove the holder stuff from it"
bincompat™️
I say we just merge this with breaking changes and drop RO entirely
why do registryobjects and deferredholders both need to exist at the same time
^^^^
bincompat™️
we can wait for a breaking change and add deferredholders and remove robs
ok but why do we need bincompat
Like, if this waits till the breaking change window we can just drop RO
why do we need phase 1
oh commoble had a different ide afrom me
my idea is that they can be separate solutions for different people and coexist
because DR can't make both of them at once
it's a fun party trick but sweeping changes like this are what breaking windows are meant for
and then I had a mental image of sweeping all the robjects out the door
DeferredRegistwo
if deferred registers are so great why isn't there deferred register 2
I just don't see the reason to keep around RO if DH exists. If the only reason is "less typing cause only one generic" that's hardly a reason
RO is the dumber older sibling of DH, we should just remove RO. if you want one generic, use a supplier
Hey, that's an idea
if you don't need DH and don't like the double generics
Why not add a method to DH to get a supplier
you can declare your statics as Supplier
why not make DH implement Supplier
DH DOES IMPLEMENT SUPPLIER
not if you make DH extends the base class in Holder
also the holder-implements-supplier is patched by forge
can we make it implement Supplier<MyBlock>?
public class DeferredHolder<R, T extends R> implements Holder<R>, Supplier<T>
Yeah, exactly
like, the method override is allowed but is the implements decl allowed
That way if you want to type less, your field is just a supplier
Given holder is an interface would any of this be easier if we made DH extend RO implements Holder instead of RO extends DH
no
Okay
Wouldn't really matter. This is all still an issue if RO is gone
it means i have to keep all the logic in RO or make RO an interface (can't do that bc of the factories)
basically makes it way more work for later to keep RO as the base class
goddamnit it can't
with the deprecation we have everything ready to go do just press delete when bc arrives
But yeah, I think making DH implement Supplier<MyBlock>` is a good solution, and maybe removing the forge patch that shoves Supplier on holders to allow that? Not entirely sure
don't need to remove the patch
wait
you can implement supplier again with a stronger type
It can if you stop forge from shoving supplier on holders
it can, it just can't inherit the default impl of get
It can? I... Did not think that worked, as a Supplier<MyBlock> is not a Supplier<Block>, which the superclass implements
is that a forge thing?
still doesn't work
To my awareness, yes
you can override get() with the resolved type cuz covariant but you wouldn't be able to automatically keep it around as a Supplier<R>
yeah if we remove the supplier patch from holder we could add it to deferredholder
goddammit you could do this in C#
yes that was a me thing
We could also just have a getSupplier() method on DH that gets a supplier of the stricter type
Also, if you want to avoid the second generic in your variables, you could also just extend DH with a custom type-specific implementation since DH won't be final unlike RO
public interface Holder<T> extends java.util.function.Supplier<T>, net.minecraftforge.registries.tags.IReverseTag<T> {
if you want a Supplier from a Holder just ::value
what would be the patch surface of porting minecraft to C#
'course that idea breaks if monag ever makes holders suppliers but we can burn that bridge when we come to it
Like, why don't we give DH a method to get a specific supplier, and have it extend holder which is the generic supplier?
it'd be bedrock but more MS
I can do this though™️ https://i.imgur.com/B7vTI34.png
Or that yeah, though we need to overload get in DH to make the returned type more specific
Which is easy
So yeah, this works great
People who want to type less get a supplier, otherwise you just specify both
oh, yeah, that does work
oh right
Yeah, but that's easy
okay, let's just take the supplier patch off holder and put it on deferredholder
for their thingy
is anyone actually using holders as suppliers
I mean, no need to do that. We can just leave it where it is and let people use ::get. Though honestly, either way works
at this point probably
it was done to make it easier to pass in both ROs and Holders to a forge patched constructor that takes a supplier
Cause if get returns MyBlock, ::get will get you a Supplier<MyBlock> even if the DH is a Supplier<Block>
is it though
yeah, if you need to pass vanilla
I mean, they're forge added suppliers
vanilla doesn't keep holder fields
It doesn't keep suppliers either and we're currently using those
just wrapAsHolder()
well yes but I can () -> STONE pretty easily
Ah, fair enough
also most things with supplier have a T constructor and a Supplier<T>
because the supplier one is for deferred /modded st uff
now this, this is an issue
Personally, I am in favor of keeping holders as suppliers, and making get on DH return MyBlock so that the DH will be a Supplier<Block> but that using ::get on it will provide a Supplier<MyBlock>
Eh, I'd say let's screw bincompat and throw it all in a breaking changes window
yeah that's easy. screw source compat
Or that, keep bin compat and break source compat
Generics don't exist at runtime anyways really
really we get two short ways to declare statics, as a bonus
DeferredHolder<Block, MyBlock> = register()
Holder<Block> = register()
Supplier<MyBlock> = register()::get```
ItemLike x = register()::get
actually it has to be even more cursed and I have to shadow R to let it infer RO<T>
screm
public <R, T extends R, D extends DeferredHolder<R, T>> D register(String path, Supplier<T> sup)
however
even this huge pile of generic garbage has an issue
if someone is, for whatever reason, manually typing register, it does not have source compat
like we do (for some reason) in this test mod https://i.imgur.com/xKONQaY.png
granted idk why it does that
it just works if removed
doesn't like entitytype registration need to be manually typed
or I might be thinking of Builder.<>of
The builder has to be typed in a lot of cases, not the RO
yeah that's the builder
I can't think of an instance where the RO has to be typed since the inference is very loose

but it's still technically source breaking
I think due to erasure this is bin breaking as well
yeah register does already have a type param
since the signature becomes (...)DeferredHolder instead of (...)RegistryObject
could just hold off on the dual generic
could just hold off on adding deferredholders until we yeet robjects :p
I could but I'm waiting for them to exist so I can stop doing this https://github.com/Shadows-of-Fire/Placebo/blob/1.20/src/main/java/dev/shadowsoffire/placebo/registry/DeferredHelper.java#L169
screm shadows that MH is slower than reflection...
ultimately a gradual transition will be smoother even if it involves a little refactoring here and there™️
for now people get to swap to DH, and then later they can implement the second generic on the DH
Do we care that much about source compat? And/or can it wait till 1.20.2?
the generic hack is bin breaking anyway so its irrelevant
Ah, yeah
personally I think that source compat example in particular is silly (nobody should be doing that)
but yeah, D is breaking since it erases to DH not RO
I hate that I can't do this as a temp workaround
public <X super T> Holder<X> weakHolder()
I can just do <X> but typesafety 
public <X super T> Holder<X> weakHolder(Class<X>) just to make it nicer for chaining
X super T is illegal Maty, that’s the problem
honestly idk why its illegal
I was fixing his idea 
bit of a weird restriction


