44import com .google .common .cache .CacheLoader ;
55import com .google .common .cache .LoadingCache ;
66import com .google .common .collect .Maps ;
7- import com .google .common .collect .Sets ;
87import it .unimi .dsi .fastutil .objects .AbstractObject2IntMap ;
98import it .unimi .dsi .fastutil .objects .Object2IntMap ;
109import it .unimi .dsi .fastutil .objects .Object2IntOpenHashMap ;
1110import it .unimi .dsi .fastutil .objects .ObjectSet ;
1211import it .unimi .dsi .fastutil .objects .ObjectSets ;
13- import it .unimi .dsi .fastutil .objects .ReferenceSets ;
1412import net .fabricmc .fabric .impl .client .model .loading .UnbakedModelDeserializerRegistry ;
1513import net .minecraft .client .color .block .BlockColors ;
1614import net .minecraft .client .renderer .item .ClientItem ;
17- import net .minecraft .client .renderer .block .dispatch .BlockStateModel ;
18- import net .minecraft .client .resources .model .cuboid .ItemModelGenerator ;
1915import net .minecraft .client .resources .model .BlockStateModelLoader ;
2016import net .minecraft .client .resources .model .ClientItemInfoLoader ;
21- import net .minecraft .client .resources .model .cuboid .MissingCuboidModel ;
2217import net .minecraft .client .resources .model .ModelDiscovery ;
2318import net .minecraft .client .resources .model .ModelManager ;
2419import net .minecraft .client .resources .model .ResolvedModel ;
2520import net .minecraft .client .resources .model .UnbakedModel ;
26- import net .minecraft .resources .FileToIdConverter ;
21+ import net .minecraft .client .resources .model .cuboid .ItemModelGenerator ;
22+ import net .minecraft .client .resources .model .cuboid .MissingCuboidModel ;
2723import net .minecraft .core .registries .BuiltInRegistries ;
24+ import net .minecraft .resources .FileToIdConverter ;
2825import net .minecraft .resources .Identifier ;
2926import net .minecraft .server .packs .resources .Resource ;
3027import net .minecraft .world .level .block .Block ;
4037import java .util .Iterator ;
4138import java .util .List ;
4239import java .util .Map ;
40+ import java .util .Objects ;
41+ import java .util .Optional ;
4342import java .util .Set ;
4443import java .util .function .BiFunction ;
4544import java .util .function .Function ;
@@ -51,46 +50,77 @@ public class DynamicModelSystem {
5150 private static final FileToIdConverter ITEM_LISTER = FileToIdConverter .json ("items" );
5251
5352 public static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean .getBoolean ("modernfix.debugDynamicModelLoading" );
54-
55- public static Map <Identifier , UnbakedModel > createDynamicUnbakedModelMap (Map <Identifier , Resource > resourceMap ) {
56- LoadingCache <Identifier , UnbakedModel > unbakedModelCache = CacheBuilder .newBuilder ().softValues ().maximumSize (1000 ).build (new CacheLoader <>() {
53+
54+ private interface ResultLoader <RESOURCE , RESULT > {
55+ RESULT load (Identifier file , @ Nullable RESOURCE resource ) throws Exception ;
56+ }
57+
58+ private static <RESOURCE , RESULT > Map <Identifier , RESULT > createCachedResourceBackedMap (Map <Identifier , RESOURCE > resourceMap ,
59+ FileToIdConverter converter ,
60+ String debugName ,
61+ ResultLoader <RESOURCE , RESULT > loader ) {
62+ LoadingCache <Identifier , Optional <RESULT >> resultCache = CacheBuilder .newBuilder ().softValues ().maximumSize (1000 ).build (new CacheLoader <>() {
5763 @ Override
58- public UnbakedModel load (Identifier key ) throws Exception {
59- var resource = resourceMap .get (MODEL_LISTER .idToFile (key ));
64+ public Optional <RESULT > load (Identifier id ) throws Exception {
65+ var file = converter .idToFile (id );
66+ var resource = resourceMap .get (file );
6067 if (resource == null ) {
61- throw new IllegalArgumentException ( "Model " + key + " does not exist in map" );
68+ return Optional . empty ( );
6269 }
6370 if (DEBUG_DYNAMIC_MODEL_LOADING ) {
64- ModernFix .LOGGER .info ("Loading unbaked model {}" , key );
65- }
66- try (Reader reader = resource .openAsReader ()) {
67- return UnbakedModelDeserializerRegistry .deserialize (reader );
71+ ModernFix .LOGGER .info ("Loading {} {}" , debugName , id );
6872 }
73+ return Optional .of (loader .load (file , resource ));
74+ }
75+ });
76+ Set <Identifier > idSet = resourceMap .keySet ().stream ().map (converter ::fileToId ).collect (Collectors .toUnmodifiableSet ());
77+ return new DynamicRegistryMap <>(idSet , key -> {
78+ if (key == null ) {
79+ return null ;
80+ }
81+ try {
82+ return resultCache .getUnchecked (key ).orElse (null );
83+ } catch (RuntimeException e ) {
84+ ModernFix .LOGGER .error ("Error loading {} {} from cache" , debugName , key );
85+ throw e ;
86+ }
87+ });
88+ }
89+
90+ public static Map <Identifier , UnbakedModel > createDynamicUnbakedModelMap (Map <Identifier , Resource > resourceMap ) {
91+ return createCachedResourceBackedMap (resourceMap , MODEL_LISTER , "unbaked model" , (id , resource ) -> {
92+ Objects .requireNonNull (resource , "unbaked model not present" );
93+ try (Reader reader = resource .openAsReader ()) {
94+ return UnbakedModelDeserializerRegistry .deserialize (reader );
6995 }
7096 });
71- Set <Identifier > unbakedIdSet = resourceMap .keySet ().stream ().map (MODEL_LISTER ::fileToId ).collect (Collectors .toUnmodifiableSet ());
72- return Maps .asMap (unbakedIdSet , key -> key != null ? unbakedModelCache .getUnchecked (key ) : null );
7397 }
7498
7599 public interface SingleBlockStateEntryLoader {
76100 BlockStateModelLoader .LoadedModels loadEntry (Identifier identifier , List <Resource > blockstateResources );
77101 }
78102
79- private static Set <BlockState > getAllBlockStates () {
80- return ((IdMapperAccessor <BlockState >) Block .BLOCK_STATE_REGISTRY ).getReferenceMap ().keySet ();
81- }
82- public static BlockStateModelLoader .LoadedModels createDynamicBlockStateLoadedModels (Map <Identifier , List <Resource >> resourceMap , SingleBlockStateEntryLoader entryLoader ) {
83- LoadingCache <Identifier , BlockStateModelLoader .LoadedModels > definitionCache = CacheBuilder .newBuilder ().softValues ().maximumSize (1000 ).build (new CacheLoader <>() {
103+ public static Set <BlockState > getAllBlockStates () {
104+ var blockStateSet = ((IdMapperAccessor <BlockState >) Block .BLOCK_STATE_REGISTRY ).getReferenceMap ().keySet ();
105+ return new AbstractSet <>() {
84106 @ Override
85- public BlockStateModelLoader .LoadedModels load (Identifier key ) throws Exception {
86- if (DEBUG_DYNAMIC_MODEL_LOADING ) {
87- ModernFix .LOGGER .info ("Loading blockstate definition for {}" , key );
88- }
89- var file = BLOCKSTATE_LISTER .idToFile (key );
90- var resources = resourceMap .getOrDefault (file , List .of ());
91- return entryLoader .loadEntry (file , resources );
107+ public Iterator <BlockState > iterator () {
108+ // We explicitly override iterator() and handle it differently so that mods iterating the maps
109+ // are likely to work with the same block many times in a row, which hits our caches better
110+ return BuiltInRegistries .BLOCK .stream ().flatMap (b -> b .getStateDefinition ().getPossibleStates ().stream ()).iterator ();
92111 }
93- });
112+ @ Override
113+ public boolean contains (Object o ) {
114+ return blockStateSet .contains (o );
115+ }
116+ @ Override
117+ public int size () {
118+ return blockStateSet .size ();
119+ }
120+ };
121+ }
122+ public static BlockStateModelLoader .LoadedModels createDynamicBlockStateLoadedModels (Map <Identifier , List <Resource >> resourceMap , SingleBlockStateEntryLoader entryLoader ) {
123+ var blockStateDefinitions = createCachedResourceBackedMap (resourceMap , BLOCKSTATE_LISTER , "blockstate definition" , entryLoader ::loadEntry );
94124 var staticDefinitions = BlockStateDefinitionsAccessor .getStaticDefinitions ();
95125 var staticIdentifiers = staticDefinitions .entrySet ()
96126 .stream ()
@@ -102,8 +132,12 @@ public BlockStateModelLoader.LoadedModels load(Identifier key) throws Exception
102132 if (identifier == null ) {
103133 identifier = BuiltInRegistries .BLOCK .getKey (state .getBlock ());
104134 }
105- var loadedModels = definitionCache .getUnchecked (identifier );
106- return loadedModels .models ().get (state );
135+ var loadedModels = blockStateDefinitions .get (identifier );
136+ if (loadedModels != null ) {
137+ return loadedModels .models ().get (state );
138+ } else {
139+ return null ;
140+ }
107141 }));
108142 }
109143
@@ -112,34 +146,8 @@ public interface SingleClientItemEntryLoader {
112146 }
113147
114148 public static ClientItemInfoLoader .LoadedClientInfos createDynamicClientInfos (Map <Identifier , Resource > resourceMap , SingleClientItemEntryLoader entryLoader ) {
115- Set <Identifier > itemIdSet = resourceMap .keySet ().stream ().map (ITEM_LISTER ::fileToId ).collect (Collectors .toUnmodifiableSet ());
116- LoadingCache <Identifier , Object > clientItemCache = CacheBuilder .newBuilder ().softValues ().maximumSize (1000 ).build (new CacheLoader <>() {
117- @ Override
118- public Object load (Identifier key ) {
119- Identifier fileId = ITEM_LISTER .idToFile (key );
120- Resource resource = resourceMap .get (fileId );
121- if (resource == null ) {
122- return NULL_SENTINEL ;
123- }
124- if (DEBUG_DYNAMIC_MODEL_LOADING ) {
125- ModernFix .LOGGER .info ("Loading client item info {}" , key );
126- }
127- try {
128- ClientItem result = entryLoader .loadEntry (fileId , resource );
129- return result != null ? result : NULL_SENTINEL ;
130- } catch (RuntimeException e ) {
131- ModernFix .LOGGER .warn ("Failed to build dynamic client item info for {}" , key , e );
132- return NULL_SENTINEL ;
133- }
134- }
135- });
136- return new ClientItemInfoLoader .LoadedClientInfos (Maps .asMap (itemIdSet , key -> {
137- if (key == null ) {
138- return null ;
139- }
140- Object value = clientItemCache .getUnchecked (key );
141- return value == NULL_SENTINEL ? null : (ClientItem ) value ;
142- }));
149+ var clientItems = createCachedResourceBackedMap (resourceMap , ITEM_LISTER , "client item info" , entryLoader ::loadEntry );
150+ return new ClientItemInfoLoader .LoadedClientInfos (clientItems );
143151 }
144152
145153 public record DynamicResolver (Map <Identifier , UnbakedModel > inputModels ,
@@ -205,28 +213,32 @@ public int getInt(Object key) {
205213 }
206214 }
207215
208- private static final Object NULL_SENTINEL = new Object ();
216+ private static final Object NULL_BAKED = new Object ();
209217
210218 public static <K , U , V > Map <K , V > createDynamicBakedRegistry (Map <K , U > input , BiFunction <K , U , V > baker ) {
211219 // TODO: support persistence of overrides
212220 LoadingCache <K , Object > bakedCache = CacheBuilder .newBuilder ().softValues ().maximumSize (1000 ).build (new CacheLoader <>() {
213221 @ Override
214222 public Object load (K key ) throws Exception {
215223 var unbaked = input .get (key );
216- if (unbaked != null ) {
217- if (DEBUG_DYNAMIC_MODEL_LOADING ) {
218- ModernFix .LOGGER .info ("Baking {}" , key );
219- }
220- return baker .apply (key , unbaked );
221- } else {
222- return NULL_SENTINEL ;
224+ if (unbaked == null ) {
225+ return NULL_BAKED ;
226+ }
227+ if (DEBUG_DYNAMIC_MODEL_LOADING ) {
228+ ModernFix .LOGGER .info ("Baking {}" , key );
229+ }
230+ var bakerResult = baker .apply (key , unbaked );
231+ if (bakerResult == null ) {
232+ ModernFix .LOGGER .warn ("Baker has returned null for {}" , key );
233+ return NULL_BAKED ;
223234 }
235+ return bakerResult ;
224236 }
225237 });
226238 return new DynamicRegistryMap <>(input .keySet (), k -> {
227239 if (k != null ) {
228240 Object value = bakedCache .getUnchecked (k );
229- if (value == NULL_SENTINEL ) {
241+ if (value == NULL_BAKED ) {
230242 value = null ;
231243 }
232244 return (V ) value ;
@@ -235,4 +247,4 @@ public Object load(K key) throws Exception {
235247 }
236248 });
237249 }
238- }
250+ }
0 commit comments