#1.21.5+ Models
1 messages ยท Page 1 of 1 (latest)
๐
Finally, we're getting rid of models. I've always said we should be hardcoding vertices into raw opengl vertex arrays \s
you are not wrong, we really should just use raw arrays of vertex data
it's obviously where all of vanilla Java MC's optimization problems lie
please... what have they done this time...
they just renamed some classes and move some of them around as part of minor cleanup.. right... right???
Of course, of course... *increases su5ed's copium dose*
(I don't actually know what the model changes in 1.21.5 snapshots are)
They are changes to the model system/s
they removed the entire model loading step, and are using Vulkan compute shaders to dynamically generate the models based on params based in code
finn
s5used
just install gander and let it handle everything for you /s
-# note: gander does not handle EVERYTHING
-# note: gander is still on 21.1 because NOPE
-# the future is now, old man
-# Oh well. Gander is quite impressive though.
-# cant wait to gander
first thing I did was porting some of the BakedModel extensions to BlockStateModel
Like what?
for blocks, BlockStateModel is a rather direct replacement
can someone TL;DR what has changed?
I am still reading up on it
So I am not the right person yet
Maybe tech can write up a couple of lines
models are the new trees
Oeh
let's just say the system was refactored more or less completely
yes
opens up kits
read model rework instead
what is that?
from 25w06a to 25w08a there were 41 reject files and 24 of them were in net.minecraft.client.renderer and net.minecraft.client.resources
I wonder how BlockStateModels are even loaded now ๐ค
I assume through BlockStateModelLoader
Not sure yet where that is called from though
What I am wondering is whether we need to extend the model loader mechanic to each of the loader kinds
am I correct in understanding that BlockStateModel is a dynamic model that selects between different Block models based on the provided state?
yes
Yep
it is ItemModel for blocks, if you want
That is at least how I read the code
you can also use it to return fully dynamic quads if you want to
so there's something called BlockModelDefinition now
https://github.com/neoforged/Kits/blob/25w06a/projects/neoforge/src/main/java/net/minecraft/client/resources/model/BlockStateModelLoader.java#L64
Tech I am sure that that loads it somehow
right, so BakedModel has become a more slender "QuadCollection"
the lengths mojang go to do not use the word "Mesh" :P
for sure yeah
my question is: how can modders create their own block models
Well the line above it
That then instantiates the UnbakedBlockStateModel
We likely need to patch at least several locations this time
....
Urยดgh
I think it's impractical to modify the block state format
at least, it would be different compared to how the system worked before
depends if we want the system to work with model inheritance or not (i.e. "parent")
if we do, hacking on top of UnbakedModel is basically a requirement
actually I think it can be reasonable clean
it is a huge blergh
My question is, for complex models do we need UnbakedModel
Or can we implement ontop of BlockStateModel / ItemModel
And get away with that
if we add something like BlockStateModel bakeBlockModel(...) to UnbakedGeometry
That seems counter to what mojang is trying to do though
With them keeping the implementation for blockstate and items completely seperate
well, the problem is that we need a way to load block state models at all
But it would be a cleanish solution
We can patch the GSON for them
Which should be fine
what gson?
Sorry
it's the blockstate json
I looked at BlockModelDefinition
My mistake
God damnit
These names are confusing as all hell
well same problem IMO; that thing would be really annoying to work with directly
Maybe unsure
you wouldn't want to inline all the json for a model inside the blockstates file
Yeah
At least for this discussion it is
That is not pushed
No it is
Github is lying again
well I can't find BlockStateModel
ARG
and UnbakedBlockStateModel has a BakedModel return
if you look here, this is the perfect place to intercept the resolvedmodel and turn it into a more complex BlockStateModel IMO ๐
what's with ResolvedModel that has a single implementation yet it has some default methods that are overridden??
oh god this is horrible for modding
BlockStateModelLoader makes BlockStateDefinitions, which either hold a MultiVariant, or a MultiPart
and both bake straight into SimpleModelWrapper
wait no multipart bakes into BlockStateModel
I don't think it's that bad
MultiVariant is the bad one
I guess we can patch calls to bakeModel
wait I see what you mean with ResolvedModel
easy fix though
we have to at least make a new method with a more generic signature
sure yes something like that
public static /*SimpleModelWrapper*/ BlockStateModel bake(ModelBaker $$0, ResourceLocation $$1, ModelState $$2) {
ResolvedModel $$3 = $$0.getModel($$1);
//TextureSlots $$4 = $$3.getTopTextureSlots();
//boolean $$5 = $$3.getTopAmbientOcclusion();
//TextureAtlasSprite $$6 = $$3.resolveParticleSprite($$4, $$0);
//QuadCollection $$7 = $$3.bakeTopGeometry($$4, $$0, $$2);
//return new SimpleModelWrapper($$7, $$5, $$6);
return ModelLoadingHooks.bakeCustom($$3, $$0, $$1, $$2);
}
conceptually of course
that assumes we don't want to mess with blockstate selectors because the traditional multivariant and multipart approaches both still work
yeah
IMO if you want to mess with selectors you do it in your custom model
at least that's how it was usually done in the past
yeah
since BlockStateModel still gets you a BlockState
you can have a MultiVariant model with a single entry
and point it at your selector model
yup
it could be nice to add API for custom loading blockstate jsons
but it isn't necessary
Seems reasonable
so blocks are not too bad
Can be something we add later
we just have 1 patch target for blockstate mapping
difficult to compose I think
- whatever we need for the models themselves
unless you can mix and match blockstate "subformats", there is no real benefit over just pointing to a custom model that can do whatever it wants
moving on, the first thing to restore I think is extra context for unbaked model
well I was thinking like, in a blockstate json, instead of selectin based on the presence of "variants", vs "multipart", have an optional dispatch key "neoforge_selector_type"
but all we gain from this is that the selector gets put in the blockstate folder instead of models folder
it's more elegant conceptually, not in code
now itemmodels ... those I don't understand yet
do they render in the update method or something?
the model prepares the render state, then it gets rendered later
(that hasn't changed too much since 1.21.4)
so the quads are cached for rendering
I'm not a fan of the addAll call specifically
it's doing more work than just setting a prepared list by reference
I'm not sure that composite should remain
vanilla already has a composite model for items
maybe for blocks, but it would be weird if it did not work for items
I argued for that during the last port....
There was some reason it was kept....
Trying to find it again
seems okay to me to have some duplicate functionality
but uh
if we make a CompositeBlockModel
it will only work with block models by default
vanilla's CompositeModel is a bit meh though because it doesn't have names for the parts
that doesn't matter for the baked model though
it only matters for inheritance
the answer is that item models are not inherited 
we could however wire up vanilla's composite item model with our part visibility extension, I think
they removed "parent"?!
oh you mean the client item stuff
yeah
oh ew I just realize this is already ugly in 1.21.4
okay so what they changed is that ItemStackRenderState now fills a list of quads, instead of passing a BakedModel by reference
... that seems worse to me
they made this specific piece of code worse
there is no bakedmodel anymore
yeah but the BlockModelWrapper contains a List<BakedQuad>
and it could set the list instead of adding its contents
since this is a baked model the list should be immutable
so there should be no issue with passing the list by reference
ok I have restored the ContextMap-based additional properties
I think now is a good time to restore UnbakedModel loaders
I will also move net.neoforged.neoforge.client.model.UnbakedModelParser.init(); to Minecraft because I don't like doing it multiple times (looks like a mistake in 1.21.4)
I am late to this party
the getModelData method you envision that the model itself snapshots the worldstate it needs to generate its quads later?
is that it?
Ah, yes ok. It's essentially snapshotting world-state though, that's what I was getting at. I hope the Javadoc reflects that ๐
what javadoc? ๐
๐
not sure how we should port applyTransform
/**
* Applies a transform for the given {@link ItemTransforms.TransformType} and {@code applyLeftHandTransform}, and
* returns the model to be rendered.
*/
default void applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) {
self().getTransforms().getTransform(transformType).apply(applyLeftHandTransform, poseStack);
}
probably as an ad hoc customization point for item models
(if it's still necessary at all)
alright, restored data-driven render types
Nice work!
thanks ๐
I think with this we should be able to port the obj loader relatively easily already
I don't think there's much value in keeping that. The ItemTransform is copied to the render state with a dedicated method now, so it would be trivial for consumers of this method to just copy the existing transform and apply whatever changes they need to the 3/4 vectors before handing it to the render state.
Not necessarily. I think it would be sufficient to provide an additional defaulted method in UnbakedGeometry which can bake to a BlockStateModel and defaults to creating a SimpleModelWrapper. That way model loaders should be able to spit out dynamic block models without splitting the whole thing into two systems
Based on reading the code I concur
That should indeed be possible
And a good solution IMHO
Yeah that's what I had in mind as well
We could also have the method on the unbaked model directly but then it would be weird with inheritance
I think that putting it in UnbakedGeometry is ideal
@spare bloom are you currently touching any of the model stuff? I'm going through compile errors in that area at the moment and want to make sure I'm not stepping on your toes by doing so
not doing anything but was gonna resume work just now
Ok, then I'll push my changes in a second and then move to entity stuff
Pushed, have fun ๐
thanks ๐
Tech, you can make EmptyModel even simpler by using vanilla's UnbakedGeometry.EMPTY
oh nice
XFact do you remember why IModelBakerExtension#findSprite exists?
there is now SpriteGetter#resolveSlot that seems to replace it, at least
Yeah, that looks like a 1:1 replacement
great, thanks
alright, now UnbakedGeometry can return a custom BlockStateModel to use
for @warped sluice see the diff at https://gist.github.com/Technici4n/14562cccb3482833d42f6b48b8c869f0
I think that with this the bulk of the work for models is done
well we had many people available for a brainstorm here which made it easier to find the best strategy ๐
Power of the community!
@spare bloom did you intentionally not wire up the render type in BlockModelWrapper or is that an oversight?
Will do ๐
We've got four remaining rejects related to models. The BlockModel reject should be resolved, unless I'm missing something. The other three (ModelBakery, ModelDiscovery and the remaining part of ModelManager) are related to standalone models. Has anyone given those any thought yet? I'm not sure what the best way to resolve those is (dumbing them down to QuadCollections doesn't seem like a particularly great idea to me)?
yes the block model is resolved. I was keeping it around until I got to exposing custom BlockStateModels
Pepper has given some thought to standalone models, see discussion in the Fabric thread
the summary is that it's not clear what to give as a final (i.e. "baked") type
Makes sense. I assume I can delete it then, correct?
yup!
Right, makes sense
so the options are either:
- simply return a standard record that contains most/all of the relevant properties
- let the model be baked into any type. basically offering a new type of "baked model" that can be customized for each application
i.e. for 2 you basically get a ResolvedModel and a ModelBaker and you turn that into whatever you want
whereas for 1 we'd extract standard state (geometry, particle, etc... everything used either by block or item models) into a record
now that I have gotten more familiar with the system, I would be tempted to say that 2 is the better option
- does not allow retrieving non-standard properties, but arguably that is not a common use case
Probably, yeah
if you wanted this to be clean, you'd probably need something like StandaloneModelKey<T> though where T is arbitary
If you use a map at all
The user could assign things to their own static fields somewhere
could be done too but it's a little bit ugly
Then the user needs to store their own keys in static fields instead
I'm not a fan of having the baker assign the result to some arbitrary static field. Baking happens async and the world, even though hidden behind the loading screen, might still be re-meshing chunks and therefore accessing these models.
Right, changes to visible state should happen in ModelManager.apply
Ok, I suppose a map is necessary then
But why not approach 1?
Unless I'm missing something, option 1 would require the models to be "dumb" and static once baked. This would make it impossible to do dynamic standalone models through model loaders
and it also makes it impossible to retrieve extra "baked" context
Pepper since last night I basically reimplemented almost all the model patches ๐
notably, additional properties following a very similar pattern: fillAdditionalProperties in UnbakedModel and getTopAdditionalProperties in ResolvedModel
Yes, but that's how it always was before. Is anyone in particular asking for the additional flexibility?
well, you have BlockModelWrapper, SimpleModelWrapper and any custom BlockStateModel that each use their own "baked" subset
so we could follow suit and let each user decide which baked subset they care about; potentially they just call e.g. SimpleModelWrapper.bake if they want to use the standalone model as a BlockStateModel
reading up, what is the purpose of the standalone model?
I'm not sure what the purpose is
Yes, possibly not needing the entire set is a consideration, but the only downside is spending a tiny extra amount of CPU time for a tiny subset of models
Models that aren't bound to a particular block or item. One use case is a model that's rendered in a BER, another would be additional geometry that is used by a dynamic block model
for "models" in BER I want to reintroduce the Renderable system eventually, which I only learned yesterday had been deleted
the goal for that is to have an object that can draw itself the way say, an entity model does without a list of BakedQuads being involved anywhere
The geometry for that still has to come from somewhere and using a baked model for that is one of the simplest options
the OBJ model already encodes the geometry in its own way
I think my initial goal was missed when updating the renderable API
cos it ended up as Mesh objects containing lists of BakedQuad
which completely defeated the point of not constructing BakedQuads that I initially had in mind
but regardless
BakedQuad or not
there's then two distinct use cases for standalone models, then
ones in which the ultimate goal is to call render(BufferBuilder) and you don't care what the internal storage format is
and ones in which the ultimage goal is to call getQuads() and you'd rather the internal storage format was already BakedQuads so this call is as fast as possible
There's no ResolvedModel patch in your gist but I assume UnbakedModel returns its own custom values and then they are composed into top values and cached in the ResolvedModel?
Yup
I would like to see more specifics of the system
I agree with it in principle but am unsure of how much it will actually be used
I suppose the same for standalone models approach 2
Neo itself uses it for render types, root transforms and part visibility. Hardcoding those is how we ended up with geometry baking context in the past
(Btw we might want to remove part visibility at some point if nothing uses it)
OBJ models use it and composite models do as well, if we can save that
For standalone models I would imagine that it could look like
public static final StandaloneModelKey<PipeModel> PIPE = StandaloneModelKey.of(resource location);
// Later:
event.registerStandaloneModel(PIPE, (resolvedModel, baker) -> /* do stuff and return a Pipe */);
And it requires a query method for later, as well
Does it need an RL?
Yes you need to know which UnbakedModel model it needs to load
Well actually that's not necessary
I don't have the code in front of me anymore
You get the general idea at least. Details idk
The API surface should manage to remain very small
Yes
I'm not sure forcing everyone to implement that is a good idea. Having to do that for the "dumbest" of models feels like bad API design to me. Most, if not all, standalone models I have in FramedBlocks for example would be RL -> QuadCollection because I only care about the quads and nothing else.
Is it not feasible to provide a simple default?
Possibly like new SimpleStandaloneModel<>(rl, (resolver) -> {...})
do we really need standalone models as a concept?
Yes
in most cases they seem to either be bound to a block or an item
Create uses them exclusively instead of ModelParts
A ResolvedModel is effectively a Pair<UnbakedModel, @Nullable ResolvedModel> at its core. You can't construct that statically, so you'd be registering yet another factory for that
ResolvableModel, not ResolvedModel
Right 
I have 2 use cases for standalone models.
primary one is this. in this case (ender-rift 1.21.4) I am using a standalone baked model, and rendering it from a BER
second one was in elements of power, where I used the renderable API so I could draw independently without having those textures stitched in the block atlas
What is even this renderable API? Sounds a bit pointless to me tbh ๐
In both cases you only need the geometry
well I may be the only person that uses it, cos it was deleted in 1.21.4 without anyone telling me
I didn't even know it existed lol
the goal for the renderable API was specifically to have an API that avoids going through BakedModel
and all the implications for it
Might have been collateral damage
and to allow animating individual parts of a model separately
spoke to Orion, apparently was removed because it depended on GeometryBakingContext
Create only uses BakedModels but it doesn't use Renderable at all so I don't really see the point either
well the point was to AVOID using BakedQuads
cos in my mind it was too many levels of unnecessary indirection, when you have an OBJModel that already has the vertex and index lists
and can push them straight into the buffer
Create converts the BakedModel and all its quads to one big int[] that stores all quads and it caches that and uses it later
apparently at some point that reason was forgotten and it ended up being a Map<String, List<BakedQuad>>
with more steps
given what the renderable system became, I agree that it was pointless
but I don't think the original idea is pointless
(and I dislike the whole "I don't use it in my mod so it is useless" mentality, so if I can find the time and effort to prove it's a good idea, I plan on doing so, even if I'm the only person using it :P)
I should note, there's value on having standalone models regardless of me doing that or not
so I'm not oposing that
I think there's use cases for both features, and standalone baked models are a reasonable substitute even if a more explicit "this is the best option for rendering custom models in an entity or blockentity" isn't present yet
Can someone define standalone models for me?
models that are loaded but don't belong to a block or item
these models are used as part of a special item renderer, blockentity renderer, or entity renderer
No, we actually bake them into the chunk too
Use case is cable parts that can also be contributed by addons, those get statically meshed into the chunk, but the "special" cable-bus model that is actually associated with the "cable bus" block cannot know all of them apriori
these models are used as part of a special item renderer, blockentity renderer, entity renderer, and other places that can include custom geometry, such as GUIs and GUI Overlays, level stage renderers, and additional chunk geometry
there expanded
:P
From the gist, I don't agree with bakeAsBlockModel. It seems like a half baked solution for use cases that should instead be covered by block model types.
So how would you implement block model types? ๐
Block model types are basically what bakeAsBlockModel does
In a weird way but the use cases overlap as I said, sure
Change the blockstates json file codec to possibly accept an object instead of the vanilla model reference string; the object can specify a "type" which works like a model loader, except that it makes a BlockStateModel.Unbaked instead of an UnbakedModel and uses a Codec instead of accepting the JsonObject directly
The type can be specified at the variant level or the variant list level instead if specifying at the model ref level doesn't work or the other options sound better. I need to check the code again to see which options are the most feasible.
That's also an option, but the patches might be gnarly and I am not sure it provides much benefit
Inlining a lot of data inside of each block state file is not great
Yeah, not a fan of overburdening the blockstate file with even more data, the files are already huge in a lot of cases. It also feels really counter-intuitive to me to either have to load the entire format yourself (or rather in practice, invoking vanilla and then un-wrapping and re-wrapping everything) with a loader specified at the root or having to potentially duplicate the same exact data across several entries instead of specifying stuff once in a model file which gets re-used by a bunch of entries in a blockstate file
Can you give me a concrete use case of bakeAsBlockModel?
Custom model loaders spitting out a model that is intended to be baked into a dynamic block model. Example:
https://github.com/XFactHD/ConnectedTextures/blob/1.21.4/src/main/java/xfacthd/contex/client/loader/ConTexLoader.java
https://github.com/XFactHD/ConnectedTextures/blob/1.21.4/src/main/java/xfacthd/contex/client/loader/UnbakedConTexModel.java
https://github.com/XFactHD/ConnectedTextures/blob/1.21.4/src/main/java/xfacthd/contex/client/model/ConTexModel.java
The example above would for example also support being baked into both a dynamic block model and a QuadCollection for an item model.
I see. I still disagree but I can't think of an alternative that I expect you would agree with.
oh yea we should probably add @stiff fossil here
I've been playing around a bit with the earlier ideas for standalone models. This is a design I came up with: https://gist.github.com/XFactHD/660cf97b214b894ac3fbd3323c67731c.
is that a new ctm mod i see? is it released anywhere, or still in dev?
-# i ask cause im not seeing it on your cursefforge projects page
-# oh lord not another CTM mod... that'd make at least 4
-# insert xkcd about standards here
Hmm why do we need the unbaked model? To mark dependencies? I didn't even notice that this is still a thing 
New is relative, it exists since 1.19.4 because I needed a test subject for connected textures support in FramedBlocks and none of the other CT mods were ported at the time. It also serves as a test subject for changes to the model API.
It's not released and I'm not sure it ever will be.
@warped sluice suggested letting people implement ResolvableModel themselves, presumably to allow baking standalone models with multiple dependencies. If we want to simplify this to only allow a single dependency, then the key can just be an RL and a baker, the unbaked stage can be a Set<StandaloneModelKey<?>> and the ResolvableModel can be created as a lambda on the fly.
I have to look at the code again. Not sure what the conditions are for a model file to be loaded into an UnbakedModel
In principle, once that is taken care of any model can be accessed through the Baker
Being loaded into an UnbakedModel is as simple as the file existing. The problem (which, as a side-note, is also the make-or-break for the composite loader) is the parent resolution and that requires handing the ResolvableModel to the ModelDiscovery
So the other unbaked models are just sitting there unused until something triggers parent resolution hmm
Composite models would require a lot of extensions to work as before
Yeah, the composite model is a major pain in the ass
With that said, I have the main sourceset compiling locally with a broken composite model and the GlStateManager errors commented out
The item stuff is already gone from my local copy. Getting the block part to work again is still a pain though because an UnbakedModel can no longer do parent resolution on wrapped models
Doesn't matter whether they are inlined or references to external files, they still won't participate in parent resolution because nothing tells them to. As it stands, the only things that can initiate parent resolution for referenced models are blockstate files, client item files and the WIP standalone model system
example, how would I implement this custom model? ```json
{
"edge": {
"parent": "morered:block/red_alloy_wire_edge"
},
"line": {
"parent": "morered:block/red_alloy_wire_line"
},
"loader": "morered:wire_parts"
}
Would it be unreasonable to let parent resolution be triggered from a ModelBaker?
I have to check what the concurrency problems might be though
IMO yes, parent resolution happens a lot earlier
Could have a pass where we let unbaked models register additional unbaked models to load?
That's more or less what you did for standalone models I think
Not home yet so I'm just throwing ideas around ๐
What I did for standalone models is the exact same thing blockstate files and client item files do
The problem with our BlockStateModel approach is that we don't have this unbaked stage where dependencies can be queried
The primary issue I'm worried about is that the recursive resolution would most likely happen in the mapping function of a Map#computeIfAbsent() call due to the structure of the surrounding vanilla code and I'm fairly certain that that's going to throw a CME (or rather, because that code uses a FastUtil map, it would potentially break in some other nonsensical way)
Depends how we do it. I think we can be smart about it
If we coordinate this over all models we can keep looping until no model has extra references to resolve? ๐ค
i.e. query models added in the last cycle repeatedly
I mean, if you can think of a more practical alternative why not. ๐ I just don't agree with doing things out of a feeling of "vanilla compliance" if it makes everything more complicated
Imagine if we added bakeAsItemModel 
So I just had a thought
We could add BlockStateModel jsons
As long as they are separate from UnbakedModel jsons 
I had that idea yesterday and am still considering it as an actual solution, at least for Fabric
HalfBakedModel jsons 
You'd need a way to differentiate block state models from unbaked models in the blockstate files
more intermediate json files 
FriedModel
is it problematic in some way, to have the "loader" stuff in the main model json now?
Loaders are used for UnbakedModels
We are discussing BlockStateModels
Currently I added a hook for UnbakedGeometry to turn itself into a BlockStateModel, but that is a questionable approach
oh I thought this was being discussed as an alternative to unbaked models
like, no more unbaked model loaders, you can either register a custom ItemModel loader, or a custom BlockStateModel loader
Yes, but I think just being able to use a ref string instead of a variant object or variant list should work
You can't really use an arbitrary block state model inside a variant anyway
Well you kinda can
item models have like { "type": "whatever" } in the json object
can we not do the same for blockstates?
It will just be restricted to some states and/or a subset of the instances
By variant I mean the object that specifies the model, rotation, and uvlock
How do you uvlock a model that's already baked and that can't be inspected?
We can but the ability to interoperate with vanilla's selection logic is worth keeping
uvlock is specified in the block state file?
yes
Yes
{
"model": "minecraft:block/acacia_fence_side",
"uvlock": true,
"y": 90
}
It would be in the ModelState
Yes that's a variant object
yeah my question was, can we extend that?
I was thinking replace it with an arbitrary codec
{ "type": "my:custom_loader",
"uvlock": true,
"y": 180
}
is this feasible?
I think is it
But Tech's point from yesterday is that allowing only that is inconvenient
yeah, but those are two different problems that are being solved
I hate the idea of having to duplicate the same data over several entries in the blockstate file when that data could be specified exactly once in the model file
That's where the idea of standalone block state model files comes in
I don't think that works: #1342994966405845063 message
Does the bake method not receive the model state?
yeah, ultimately I would prefer to keep the custom loading for models (Quad list providers) be declared inside the model json
but the logic for model selectors makes more sense in the client item json / blockstate json
No, only the baker. ModelState is only passed to ResolvedModels to bake their geometry.
That's not even being discussed rn
My biggest issues with the current system (that I implemented yesterday) are
- dependent models
- that a model could be used either as a BlockState model or a QuadCollection and kinda needs to support both (or using the wrong one would lead to a silent failure)
And of course the asymmetry with ItemModels (ask yourself why don't we just add bakeAsItemModel), but that's more of secondary concern to me
there's a little part of my brain wondering if it wouldn't be worth making a separate ser of file formats, mesh definition jsons, mesh processor jsons (can select / combine meshes), and in-code mesh adapters for turning them into blockstate models and item models as needed
I don't like that part of my brain though
So abstraction for the sake of abstraction?
it's just the part of my brain that doesn't like working with mojang's limitations speaking
Sometimes, all we need is just a little bit more AbstractFactory ๐
beats working at the concrete factory
Yes, or in the hellish mines of Impl
Hmm, I have a feeling that my fix for composing the root transform into the ModelState isn't quite correct ๐
(Yes, that's a technically working composite model with an experimental solution for parent resolution)
alright I'm finally back at my PC let's see
Hmm, that's not right either 
LGTM. ship it
if we wanted to patch block model types, we would simply have to patch them in Variant
we can add a block_model key relatively easily
looks a bit like a nightmare with visualEqualityGroup though
What would that reference? A new format in a dedicated directory or models in vanilla directories?
a new format
the main advantage of such a thing is that you'd be able to easily nest BlockStateModels
I don't understand how this is supposed to work
essentially MultiVariant#bakeModel would have to bake the BlockStateModel referenced by the Variant
Do you intend on patching BlockStateModel.Unbaked#bake to receive a ModelState?
could be done yeah
I don't think it's necessary
honestly I'd probably introduce a different class than BlockStateModel.Unbaked since we have to provide a full dispatch codec for it and I wouldn't expose visualEqualityGroup
of course, moving things out of the block state files makes matters a bit more complicated
@hardy talon what kind of use case do you have for blockstate files + custom block models?
(i.e. giving a different custom block state model to various states or parts)
CTM?
Yup, the CT model is the primary one
I have a case where I have a multipart blockstate where each state gets a different vanilla-style model but I also apply a dynamic model to all states
However, the CT model has at most one parent which would be returned by UnbakedModel#parent() and be handled transparently by vanilla. The problems start once you have multiple dependencies like the composite model, Commobles use case or this one:
https://github.com/XFactHD/RedstoneControllerUnit/blob/1.21.4/src/main/java/io/github/xfacthd/rsctrlunit/client/model/ControllerModelLoader.java
https://github.com/XFactHD/RedstoneControllerUnit/blob/1.21.4/src/main/java/io/github/xfacthd/rsctrlunit/client/model/ControllerGeometry.java
https://github.com/XFactHD/RedstoneControllerUnit/blob/1.21.4/src/main/java/io/github/xfacthd/rsctrlunit/client/model/ControllerModel.java
yeah, a BlockStateModel.Unbaked would have no issue dealing with that
Yeah
btw, do you know why vanilla adds the coloring properties to the group key?
it made sense at some point but now I don't understand it
the properties are the same for all block states of a block, and each block has either a different MultiVariant or MultiPart instance and therefore visual equality group? 
To be honest, the entire model group system makes little sense to me
ah right now I remember
it allows avoiding chunk remeshes
this here
if you change between two block states that have the same "visual equality group" no remesh is requested
that's why it also needs to take into account the properties that affect the color of the blocks even if the models don't use them explicitly
Makes sense
Fixed the root transforms (the emerald block is already broken in earlier versions, still need to figure that out - I think Pepper mentioned something related to that at some point).
Subject: [PATCH] ModelDependencies
---
Index: projects/neoforge/src/main/java/net/minecraft/client/resources/model/ModelDiscovery.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/projects/neoforge/src/main/java/net/minecraft/client/resources/model/ModelDiscovery.java b/projects/neoforge/src/main/java/net/minecraft/client/resources/model/ModelDiscovery.java
--- a/projects/neoforge/src/main/java/net/minecraft/client/resources/model/ModelDiscovery.java (revision d2f29c0063794941428c3ee647b5ab960856279d)
+++ b/projects/neoforge/src/main/java/net/minecraft/client/resources/model/ModelDiscovery.java (date 1740428360518)
@@ -63,6 +63,8 @@
ModelDiscovery.ModelWrapper modeldiscovery$modelwrapper = new ModelDiscovery.ModelWrapper(p_405734_, p_404997_, flag);
if (!flag) {
this.parentDiscoveryQueue.add(modeldiscovery$modelwrapper);
+ } else if (p_404997_ instanceof ResolvableModel resolvableModel) {
+ resolvableModel.resolveDependencies(this.resolver);
}
return modeldiscovery$modelwrapper;
@@ -113,6 +115,9 @@
} else {
p_405689_.add(modeldiscovery$modelwrapper);
}
+ if (modeldiscovery$modelwrapper.wrapped instanceof ResolvableModel resolvableModel) {
+ resolvableModel.resolveDependencies(this.resolver);
+ }
}
}
The composite model then implements ResolvableModel:
@Override
public void resolveDependencies(ResolvableModel.Resolver resolver) {
for (Either<ResourceLocation, UnbakedModel> child : children.values()) {
child.ifLeft(resolver::markDependency);//.ifRight(model -> model.resolveDependencies(resolver));
}
}
The way this works of course means that inline models in a composite model are dead once again (sorry Soaryn, deal with it)
is there anything in vanilla that prevents infinite parent cycles?
Yes
ModelDiscovery#resolve() has a check for it
ah yes I see
say we introduce block state model types and we make composite models a block state model
how will that work with part visibility?
It would have to load the part visibility from the blockstate model and ignore the one specified in the "standard" model
these separate block and item models kinda suck
we should really add bakeAsItemModel ๐
well at least item models can query additional properties from the ResolvedModel now
I assume that would effectively be a copy of BlockModelWrapper.Unbaked#bake(), correct?
it could in principle merge the part visibility of the ResolvedModel with that of the block state model
yes
WTF is RegistryContextSwapper
it swaps a registry context
As far as I understand, it's a cursed way of switching out the dynamic registries under the feet of item model properties without having to reload resources when connecting to a server such that the item model properties can check data components which hold dynamic registry entries
that sounds about right
so I guess we have two options now:
- roll with what you have when it comes to model dependencies and accept that unbaked models can't be nested in the same file
- introduce block state model types, move composite to it, remove visible parts additional property, don't allow adding additional unbakedmodel dependencies
(we don't have to remove visible parts but I think it makes more sense if we move composite to a block state model type)
Wouldn't all the ResolvedModels in this case be the part models though? I assume making the composite model a blockstate model would move both the children and the visibility stuff entirely to the new file format.
yeah you are right; so we'd have to remove the visible parts additional property
well except that obj uses it? blergh
Yup
we would be removing visibility from the block state model format entirely
since it can't be overriden, it wouldn't make sense to keep it
in fact, similarly to how CompositeModel.Unbaked only has a List<ItemModel.Unbaked> models, the block state model equivalent should only have a List<BlockStateModel.Unbaked> models
you could have it so that
{ "model": "xxx" }
selects an unbaked model whereas
{ "type": "xxx" }
selects a block state model
(so a model key would actually be how you encode an "unbaked `SimpleModelWrapper")
so a composite model would be something like
{
"type": "neoforge:composite",
"models": [
{ "type": "ctm:whatever", /* extra data */ },
{ "model": "mymod:additional_piece" }
]
}
OTOH since you can create arbitrarily many model files, there is no need to inline the definitions of nested models in the same file, it's just convenient sometimes
alright I've made my decision: we keep bakeAsBlockModel
I may have a slightly cursed idea for how to make the composite loader work exactly as before
do tell
allow models to inject arbitrary (already parsed) UnbakedModels? (along with a ResourceLocation to retrieve them later of course)
I should fairly trivially be able to construct my own ResolvedModel for the inline model. With the experiment from earlier, I can mark the inline model's parent as a dependency and then retrieve that parent model as a ResolvedModel from the baker
yeah that should work lol
that's probably more or less how it worked in 1.21.4 anyway, since the UnbakedModel also acted as the ResolvedModel
Yup
ok why not
btw can you push what you have already? I think we can always make UnbakedModel implement ResolvableModel
it might make sense to add an extension method to the Baker or something to construct a ResolvedModel from an UnbakedModel
e.g. resolveFreestanding(UnbakedModel model, Supplier<String> debugName)
not to be confused with standalone 
I'm gonna test it real quick and then push it. I think it would make sense to keep "resolvable unbaked models" opt-in though
well, I would suggest that we default-implement markDependency for UnbakedModels
implementing a magic interface is not great API design IMO
Fair
Yup, works. Left is with references, right is inline
Pushed. The way it's currently implemented means that items referencing a composite model will render nothing. If we do decide to introduce a bakeAsItemModel() extension, then this could be fixed by converting our composite model into the vanilla composite model
I wonder if we should at least make the normal bake error out when it is called
I had that thought as well, yeah
if we throw it will probably fail the whole reload though
It shouldn't, ModelBakery#bakeModels() try...catches model baking on a per-BlockStateModel and per-ClientItem basis
oh yeah, nice
Btw, for standalone models: should I push what I have for that currently (i.e. the patch I linked last night) and we potentially simplify it later or do you want to discuss it further first?
I think you can push it
๐
BTW, how much thought did you give to the cycle-finding algorithm and nested dependencies for UnbakedModel#resolveDependencies?
Just a tiny bit. It should work fine for most cases but there are likely cases where a dependency from UnbakedModel#resolveDependencies() can bypass it
nested computeIfAbsent calls seem to be a problem, no?
That's the one thing I'm not sure about. At least in my experiment with the composite loader it worked fine
it's only a problem if the table gets resized and/or the bucket is taken by something else
Yeah, that makes sense. Worst case we change ModelDiscovery#getOrCreateModel() to first do a containsKey() check and then do the recursive resolution on the return value of computeIfAbsent() if the key wasn't previously present
yeah
and regarding cycles, what can happen is that cycles are not detected in some cases as far as I can tell
Yeah, I would suspect that a composite model referencing itself as a part would be missed
btw what is the point of the resolveDependencies call in discoverDependencies?
shouldn't it already be covered by the call to getOrCreateModel?
Both getOrCreateModel() and discoverDependencies() calling resolveDependencies() is due to the distinction between root models and non-root models (the latter being ones that specify a parent). In hindsight I'm not sure that's even needed. I should be able to test that fairly easily though, one second
I'm gonna push the standalone models first though
another way to see is that there is a single instantiation site of ModelWrapper
cursed formatting ๐
๐
not a fan but I'm not going to change it ๐คท
another thought: we could probably fix the cycle detection if we had Set<ResourceLocation> getAdditionalDependencies() instead of void resolveDependencies(Resolver resolver)
That's true, yeah
basically we'd change the definition of root models to models that have no parent and no additional dependency
makes propagateValidity a bit annoying though
The ModelDiscovery.ModelWrapper will then also need a Set<ResolvedModel> additionalDependencies and the non-null parent check in discoverDependencies() needs to be relaxed
This is an interesting view 
WTF ๐
sounds annoying tbh
it would be a Map<ResourceLocation, ModelWrapper> I think
The test run still crashes when trying to create a world, so I had to test somewhere else and in that environment it was easiest to replace vanilla models ๐
Probably, yeah
Presenting somewhere else 
Ok, moved the recursive dependency resolution to a single place and changed getOrCreateModel() to avoid issues with doing the resolution within the computeIfAbsent() call
lol I also did that already 
lmao
slightly differently though
What does your solution look like?
Heh, funnily enough, I was thinking about doing that right as I pushed it 
Feel free to change it
alright, changed ๐
๐
I don't think that we really need to care about cycles
We can always come back to it if it turns out to be an actual issue
Yeah
I'm really not a fan of the Supplier<UnbakedStandaloneModel<T>> modelSupplier
It really begs the question what the use case of providing a custom ResolvableModel for this is. If we decide that a standalone model should only have a single dependency, then we can simplify the key to StandaloneModelKey<T>(ResourceLocation modelId, StandaloneModelBaker<T> baker), store the registered keys as a set before baking, change the dependency resolution to standaloneModels.forEach(key -> modeldiscovery.addRoot(resolver -> resolver.markDependency(key.modelId()))); and invoke the custom baker directly in StandaloneModelLoader.bake()
after all, you can always do arbitrary magic with model loaders and additional dependencies
Yup
in any case what I had in mind was StandaloneModelKey<T>(ResourceLocation name)
and you'd register the key + the baker to the event
with that design we can also remove the StandaloneUnbakedModel for now and add it back later if there is demand
Any particular reason to keep the baker outside the key?
the fact that it's a key? ๐
Fair enough ๐
do you want to do it? should I?
I can do it
hmm, should we ensure that no two models share the same ID?
in principle multiple mods may want to load the same model
Yeah, two keys could realistically point at the same model file
yeah
we should document that, it's not obvious that the keys are compared by identity
btw, it's also worth considering that the event is called RegisterAdditional but the models are called "standalone"
I can rename the event if you want
That reminds me: I need to make the key actually compare by identity, it's a record so it would currently be compared "by value" in some areas (specifically in the map returned by the ParallelMapTransform)
I think it would make sense for consistency. Standalone models are a very distinct thing now, even more so than in 21.4
stupid question: why is it a record?
Because why not, that's it ๐
if we need identity it might be better to make it a simple final class
Yeah, that's what I just did
There we go, custom unbaked model gone
Now the only remaining things are the GlStateManager compile errors, the test framework and the tests and two fluid-related rejects
Yup, me too
we might end up with the fluid mess in the main repo if nobody wants to fix it (I don't!)
is it time for the users of the system to fix it? ๐
I don't really want to either, I already broke it once during the 21.4 port 
we need a willing sacri-- I mean, volunteer to fix fluids
I can look into the tests tomorrow
ok only shader stuff left in testframework
@spare bloom I quickly ported my CT implementation last night and I'm currently fixing the model test mods. I'm considering (re)introducing a couple things:
DelegateUnbakedModel: useful for models that load and bake a single inline model as they need to forward all of them (applies to my CT mod and several of the tests)DelegateBlockStateModel: nice to have since we introduce several more methods toBlockStateModelwhich require forwarding to wrapped models (was previously vanilla'sDelegateBakedModel)- The helper you mentioned yesterday for getting a
ResolvedModelfrom an inlineUnbakedModeltoModelBaker: basically required for any unbaked model that wraps one or more inline models (applies to my CT mod, several of the tests and the composite model)
Any comments or objections?
No objections ๐
Regarding the third point, I guess make sure to document that the dependency has to be marked in the resolver earlier
That's the thing: it doesn't necessarily have to be, it depends on the case. In the composite model's case it does have to be marked as a dependency because there are multiple inline models and the outer model doesn't forward anything from any of them. If you have a single inner model though and forward that model's parent as the outer model's parent, then the dependency resolution is taken care of for you
True
IMO call it delegate not parent
Parent is confusing for unbaked models
Also I would suggest ModelBakerExtension, i.e. without the leading I
I called it IModelBakerExtension because the previously existing extension interface was called that. I can rename it if you want
That's fair, will change it later
nice design you got there
would be a shame is somebody... merged some additional refactors
Refactor it all
looks at that part of his brain and hopes it isn't right
xfact, if models changed a lot, please help me with the connected texture model again 
Hehehe, will do ๐
the refactors will continue until morale improves
Continue refactors until no mod loader needs to make any extensions to the system
"just one refactor more and it will be alll perfect, trust me"
boq wants to refactor our brains so they stop having too-high expectations
You refactor and we redesign, not problem ๐
I prefer no I for interfaces, this is not C# ๐
oh no, not this again 
Block models got the item treatment it seems
> this.singleThreadRandom.setSeed(blockState.getSeed(blockPos));
> blockStateModel.collectParts(this.singleThreadRandom, this.singleThreadPartList);
> this.modelRenderer.tesselateBlock(blockAndTintGetter, this.singleThreadPartList, blockState, blockPos, poseStack, vertexConsumer, true, OverlayTexture.NO_OVERLAY);
Aha they did switch to a collector approach like FRAPI uses
They are just collecting parts instead of quads directly
It doesn't matter too much as FRAPI can still patch emitBlockQuads onto BlockStateModel, but it's annoying that the vanilla model renderer doesn't accept the model directly
I suppose an alternative can be added for that too
Is it of type QuadCollection
List
I mean the type that has that method not its return type
I know about the SingleModelWrapper
The part being a QuadCollection makes sense
Well for vanilla this is a great change and maybe it gives Neo another reason to reconsider its getQuads
Models changed again? 
Yes
as was foretold
Boq basically said this would happen
the 1.8 paradigm of baked models is finally gone
Will Neo adapt?
To what?
So what you are saying is it is finally time for all the 1.7 holdouts to port
won't happen
probably, to some degree
The 1.8 paradigm of baked models being gone
the whole extended getQuads thing can probably be replaced by just extending the part collection logic
The extended getQuads is part of that paradigm
That would be my suggestion yes
To be honest the getQuads still exists just in a different place now
The question I have is where we will extend
IMO extending part collection makes way more sense
I would think it would now be on the part collection
Yes from reading the code I would tend to agree
I think this makes sense too
as a part collection is now a collection of quads, and the configuration for those quads
Yeah
It's not the full configuration
Neo does not store the RenderType like other quad properties
IMHO RenderType should be part of the part collection
Not directly on the Quad....
But maybe not
Then again the model AO is also a separate property, but Neo's AO control is per-quad
It's more memory efficient to store it on the part collection than per-quad
Anyway, in vanilla BlockStateModel is everything that does (BlockState, Random) -> something, and is unconcerned with rendering.
I was even considering just making parts generic
The renderable bits are just in Variant
it makes more sense logically
Really what I want to avoid is having whatever extension method be called more than once
BlockStatePart is essentially sub-models(?)
they should store state
quads should not store state
Meh sure but in practice we need a lot more flexibility
How do item models and overrides work
my thought is we would give world/ModelData context to collectParts
that seems reasonable
That seems like the simplest solution
I hope this ends up being the case though
A RenderType parameter will not allow that
just more parts implementation than Variant, like multilayer of whatever
do if (part instanceof SpecialPart special) doMagic(special) if you need
I am not saying to remove the functionality, but to reimplement it in a way that requires calling the method only once instead of once per RenderType
That would mean a different return type
I think we just move the render type field from SimpleBakedModel to BlockModelPart
And I am not sure if that is worth it
The call cost to that method are most likely much lower then the iteration costs of the relevant parts that get populated.....
But something I would need to see the numbers on
Or hacks of a subclass and then instance checking the result. But that seems ugly
Unsure
I mean maybe....
I don't know
It is difficult to know what rendertypes are needed on a given model from the outside, unless we expose that information somehow (aka getRenderTypes on BlockStateModel)
So that mimicking models can pass that through
or somehow pipe that information around
And retrieve the right quads for the right render types from their respective inner models
If the RenderType is in the part, getRenderTypes is no longer necessary
The BlockState == RenderType bound has always been a bit tricky to handle
Hmm maybe
But difficult to handle
We just move the extension methods to BlockModelPart, no?
Because the level renderer needs to filter out all the parts it does not need in that stage
Which seems like a massive overhead/waste
We should refactor that to allow all the render types to be rendered at once
My initial instinct said yes, but thinking about it pepper is right to some degree and then this would not directly make sense
We can't do that
Yes, during iteration of parts, just select the correct buffer
Sure we can
Because for one, we fire an event per rendertype, and for two that is simply not possible with things like transparent etc
I'm referring to the logic in SectionCompiler
At least not in the way the level renderer was written in the past
What's the problem? Might iterate a bit unnecessarily but that's no different from a model returning no quads for some type
Hmm yeah there we potentially could......
That is actually true, but I guess that entirely depends on the quad count
Sorry part count
Not quad count
It doesn't. It depends on the part count and it probably is quite low in practice, especially compared to the cost of processing the quads
Yeah as I said
Ah yes part count
Wrong word
You might be right.....
It might be worth thinking about for a bit
And then go for this route none the less
As it seems to be the easiest option IMHO
Potentially with embeddeds idea to just render the entire model at once to all buffers in the compiler
It's not really different from a model with many submodels in the previous system
Yes.........
Which is why I feel like it could work
But it is a "feeling"
Could be done, this is how FRAPI pipelines work after all
I know, hence me suggesting that his idea for the section compiler will likely work that way
IMHO We could do that for now
- Expose getRenderTypes on BlockStateModel (and maybe ItemModel?)
- Rewrite SectionCompiler to get all rendertypes
The other alternative:
- Don't expose the getRenderTypes()
getRenderTypes is not needed for item models
- Adapt the SectionCompiler to not give a shit and handle it internally
(you already choose the render type for each item layer)
Yeah true
Why does it need to be a list like that?
can't each part just have one RenderType
Fundamentally it doesn't change much but it would be an option
it just makes more logical sense to store state more statically
ah I see boq's warning was real. tl;dr?
where state only changes between parts
That is what I am trying to determine.....
Right now we can trivially say, as a wrapping model, in what rendertypes we render by asking the inner model.
That makes specific speed ups possible, by handling the render type variations properly.
The section compiler implementation is basically vanilla and that makes maintanance easy
The counter point is that if it is stored within the part then that makes models easier to implement but more annoying to optimize (as you have less data available to you)
And it would make the section compiler harder to maintain, as it now contains a significant patch to handle all the states at once, or hit a "massive" speed bump because it iterates over all the parts over and over again trying to find the parts that match
And we could potentially do both
Where getRenderTypes() should be considered a hint
Why is it iterating multiple times?
Ok I see what was done. For sure requires a bit of thinking on our end
Keeping the parts "dumb" matches item models best
The section compiler (as far as I read it right now) just grabs the render type from the static map, and considers all parts to have that type, and passes it to the right buffer, no?
We patched that so that it would iterate over all render types that the model told it to, and then call getQuads for that, if the SC was currently doing that RT.
Now that is not directly possible anymore.
There are now two options.
Either we keep the SC behaviour that it is responsible for one RT at a time, add RT to the Part, and then filter out all parts that do not have the RT at all. Which is wastefull.
The third option is getting the correct buffer for each part as the iteration happens a single time
That would require "Smart" parts
Which is not inline with the design mojang has
As Tech already points out
Rigth now from reading the BlockStateModel and ItemModel code
I would prefer the "Dumb" solution
Where parts are dump and just carriers of the Quad+Config data
And pass, world/modeldata/rendertype to the getParts call
However there is an argument that rendertype is part of the part configuration
And so should reside there.......
That cannot be the only reason because this reason was ignored when making design decisions for model extensions in the 08a port
Which is likely your argument no, pepper?
Yes
It is at least one of the reasons. Another is that removing getRenderTypes simplifies the work that model implementations have to do.
An analogous reason for why I prefer FRAPI instead of Neo's existing model extensions is that I only need to override a single method instead of 4 different methods
I can understand that completely
My biggest arguement against the FRAPI style is that it is not how Vanilla does it, and for many modders, that is generally the best resource they have on how to do and use stuff in the game
I'm trying to catch up but I'm having trouble finding how the game now resolves blockstates into models
That is done already before hand, in Variants
so models are now baked combinatorically? I can't see a cache at a glance
oh I assume collectParts is being called when processing chunks?
gah why does my scarce social life have to coincide with mojang's snapshot cycle so often
I don't think a section compiler patch would be very hard to maintain
It would not
We already have one to limit it to a given RT
Every design decision is a careful tradeoff
You saw the extensive discussion 2 days ago where we considered whether to implement modded unbaked block state models
We sadly don't have the luxury to rewrite systems every version, so API designs are carefully considered for all kinds of aspects.
Including but not limited to similarity with vanillas design, as we strive to follow it as much as we can
Even if that limits us sometimes
True, ignore was not the right word
I can understand that you find it at least a bit strange that we put so much weight on it
Or at least more weight then would seem to be obvious at first, especially since mojang it self does not consider our cases (nor should they) when they design systems
No, actually the opposite, because bakeAsBlockModel is not in line with Mojang's design. As Tech said the discussion did happen so a lot of thought was still given.
I haven't had time to properly acquaint myself with the code (away from home again), but I see it as a tradeoff between maintainability (less patch points is better), usability (less complexity is better), and usefulness (more use cases is better)
Completely true, does not mean we follow it always
Yes
And we might alter our opinion on bakeAsBlockModel as we now finalize the approach for this
right now my worry is dynamic block models, but I assume Orion already has block bits in mind
Dynamic models should not care whether the part has the rendertype
Or it is passed into the getParts call
as I said, I haven't had time to catch up with changes, so I don't know if producing dynamic quad lists is part of the problem or not
The problem with these refactors is usually not whether something is still possible but rather what the best way to make it possible is
Yep
I keep a hawk eye on whether dynamic models are possible with this
But given that like out of the 6ish rendering people we have on the team
Each and every one of them has some form of dynamic model
I am not really worried that anybody forgets it
Completely
These model parts are actually quite great because they solve a big UX issue with complex models which is the composition of multiple submodels
Agreed
As a somewhat unrelated note, we should probably think about introducing some sort of MutableBakedQuad helper. That and composition are IMO the biggest selling points of FRAPI
a) that building of map was how it worked for last couple versions, but it might be clearer now
b) cache is used in UnbakedRoot implementations
Introspecting and on the fly alteration without memory overhead
Yeah basically
Net change is that any entry in block state definition is baked only once, even if it covers multiple blockstates
which, on vanilla brought a puny save of about 10MB, but it's still something
While the approaches are functionally equivalent it is interesting to think that if parts are collected exactly once per model, a significant advantage of getModelData is lost. It does precomputation using world data so that computation does not have to be redone every time the method to receive geometry is called, but if it is now only called once, then precomputation is no longer necessary. I don't deny that ModelData has other uses but I am just saying that if this advantage does disappear then perhaps it merits a rethinking of how ModelData works.
The whole idea of caching model data is that the model should not touch the world at all
At least not on the meshing thread
Pepper means the getModelData method that receives the block and tint getter
BakedModel.getModelData already runs on the meshing thread: https://github.com/neoforged/NeoForge/blob/1.21.x/patches/net/minecraft/client/renderer/chunk/SectionCompiler.java.patch#L21
It has a world parameter, so the model does have access to the world
(level in the chunk meshing copy sense)
In vanilla it's the RenderChunkRegion
Okey, I missed when that was added
Because the whole idea of ModelData
The core problem it is supposed to prevent
Is people accessing the world
Because like it or not, that caused so many issues in the past
With people accessing their BE
Or any other structure in the world that was not thread safe
And caused so many issues
I can't believe we're having this discussion again ๐ฆ
I'm confused because it was added a really long time ago
The existence of this function might not be obvious at first but it's the only way to do CTM
Right, it's an absolute requirement
If BE access is problematic, patch the BE getter to throw or something
(And CTM is just a simple example. In general any world state access that is not BE access should potentially use it)
That method has existed since the inception of ModelData. It's the only sensible way to allow things like connected textures without providing the level directly in getQuads(). Accessing the level in it is perfectly safe as the provided level is specifically designed to provide safe access to world state on meshing threads. Accessing a BE's internal state is the only case where you would realistically run into issues
doesn't getBlockEntity return null if you call it offthread anyway
Only on a ServerLevel
ModelData can be used as a cache key....
The point is: Accessing a BE from a custom model == bad
It always has been
But yeah we need a way for custom models to access the world to do CTM etc
It can't
Yes, but none of the actual use cases of BakedModel#getModelData() need that.
Some mods such as AE2 shove the ChunkRenderRegion inside of it
Does not mean it did not happen in the past
I'm well aware that this was regularly done wrong (ignoring the fact that the pre-1.19 ModelDataManager required it when mods with fake levels came into play). In fact, I could link you an example where it's still being done and actively causes issues.
Well that brings up a different question.
Do we want to expose a cache key?
And if it is still not thread safe do we want a different design?
we discussed it and it might be a good idea
From what I remember a good choice is for the model to have a dedicated method that returns an arbitrary Object key given the same context that geometry retrieval receives
Yep me too
If we are already going through this with a sledge hammer
Should we do it properly
And tackle these two aspects?
It is already thread safe
The only solution for BE access is to patch the vanilla method to throw
it should be done separately from the other changes IMO
Yes agreed
But lets just at least theoretically discuss it now
I would concur
Would that be a problem for any known model implementation currently?
in principle if the BE is careful to be thread safe it's ok
I cannot use Fabric render data/Neo ModelData for that as I cannot mixin to every single BE, but even if I could, mixins should not be necessary to achieve a use case
Yes...... But you know....... Most idiots are not Pepper and even in this case pepper can not really guarantee that what he does is safe and won't blow up..........
I'm not interested in "modders are stupid" discussions
Except I am
Because we are supposed to properly design an API
That prevents people from shooting themselves and many others in the foot
And as XFact also pointed out, that foot shooting actually still happens
Eventhough it was the original design goal ModelData was suppose to fix, that that particular detail somehow got lost on the way, does not make it less relevant
Because if you leave that design part away, then modeldata has virtually no use, as you can just access the world, and through it the BE
And grab your data
what I mean is that we all know (or so I hope) that this is part of the design, and it is pointless to bring it up again
Yes
Assuming you mean BlockEntity.getModelData and BlockGetter.getModelData
Whether it needs to be a map is debatable but those methods must exist regardless
if you're going to make rules, you shouldn't be worrying about what happens when they're broken
that's why you made the rules

Sorry @wide oak But this feels entirely of topic or not matching the conversation. If you want to contribute as a sodium maintainer / iris maintainer on how to make custom model access to BE data thread safe, feel free to do so, but that comment is not helping any body forward
Interesting
Everybody in here knows, hence me saying that Pepper likely does his stinking best to make the name retrieval as safe as possible, but likely can't guarantee it fully (because he simply does not control the underlying implementation for 100%)
However that does not and should never mean, that we design an API in such a way that it requires reading a bunch of documentation and its minutia to create a simple model loader that accesses your data on your own BE.
It is not common knowledge for the modder that getModelData in the model it self is called from the meshing thread
in fact I added Called, typically on a meshing worker thread, to capture additional model data needed by the model. to the javadoc 2 days ago
previously there was no javadoc whatsoever ๐
Well that is a start..... And kind of makes my point at the same time
By the way, patching the BE getter to always throw won't work, because vanilla itself sometimes uses it in a way that is not necessarily thread safe
I've been skimming through the chat trying to get a rough idea of what model changes happened, and they seem pretty conveniently in line with what my instance-based geometry baking stuff in Gander is doing. In particular, the BlockModelPart stuff is more or less the API I was looking for
it's not instance-based and not really static
The instancing stuff is just how I implemented it; the querying and baking of BlockModelPart-s seems to be pretty much exactly what I had in mind
Specifically, you collect all of the BlockModelPart-s once and bake them all, it appears
then the baked models just refer to those BlockModelPart-s
Dynamic parts from custom models are possible
The object instance may be different every time you call the method
That is true, but it seems like the intent here is that the BlockModelPart instances could be shared
Or, at the very least, the actual mesh data behind each BlockModelPart could be shared
You have it upside down
and BlockModelPart itself could be a "this mesh data with this configuration" object
The idea is that the model can be shared
But the parts can not
They are still heavily specific to the context
And are fully dynamic
The code I'm reading doesn't seem to suggest that at all
why do we need BlockEntity#getModelData?
IIRC I just stick my model data in savedata and then have the other getModelData grab that
wat
The idea is that the BlockEntity sticks its data in that model data instance that should be access from the model loader it self
And that data is actually cached these days in the modeldatamanager
Only updated when need be
So that the model in and of it self does not need to touch potentially unsafe for multi-threading data in your BE