minerender 2.1.0-alpha.7 → 2.1.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -852,6 +852,7 @@ declare class EntityObject extends SceneObject {
852
852
  constructor(entity: EntityModel, options?: Partial<EntityObjectOptions>);
853
853
  init(): Promise<void>;
854
854
  dispose(): void;
855
+ private get textureKey();
855
856
  protected loadTextures(): Promise<void>;
856
857
  protected createMeshes(force?: boolean): void;
857
858
  protected applyTextures(): Promise<void>;
@@ -1511,6 +1512,9 @@ declare class Skins {
1511
1512
  }
1512
1513
 
1513
1514
  declare class TextureLoader {
1515
+ private static readonly failedTextures;
1516
+ /** Reports whether image loading failed, so cached textures can be retried. */
1517
+ static hasFailed(texture: Texture): boolean;
1514
1518
  protected static createTexture(): Texture;
1515
1519
  static loadInBackground(src: string, format?: PixelFormat, rotation?: number): Texture;
1516
1520
  static load(src: string, format?: PixelFormat, rotation?: number): Texture;
@@ -1620,7 +1624,7 @@ declare class BrowserCache extends PersistentCache<LocalForage> {
1620
1624
  *
1621
1625
  * `image-size` pulls in `fs`/`path` at module scope, so it can only live in the Node build.
1622
1626
  * Everything MineRender loads is a PNG in practice; GIF and JPEG are handled as a courtesy and
1623
- * anything else falls through to an empty result (callers treat that as 0x0).
1627
+ * anything else falls through to an empty result, which ImageLoader rejects.
1624
1628
  */
1625
1629
  declare function probeImageSize(data: Uint8Array): ImageSizeInfo;
1626
1630
 
@@ -852,6 +852,7 @@ declare class EntityObject extends SceneObject {
852
852
  constructor(entity: EntityModel, options?: Partial<EntityObjectOptions>);
853
853
  init(): Promise<void>;
854
854
  dispose(): void;
855
+ private get textureKey();
855
856
  protected loadTextures(): Promise<void>;
856
857
  protected createMeshes(force?: boolean): void;
857
858
  protected applyTextures(): Promise<void>;
@@ -1511,6 +1512,9 @@ declare class Skins {
1511
1512
  }
1512
1513
 
1513
1514
  declare class TextureLoader {
1515
+ private static readonly failedTextures;
1516
+ /** Reports whether image loading failed, so cached textures can be retried. */
1517
+ static hasFailed(texture: Texture): boolean;
1514
1518
  protected static createTexture(): Texture;
1515
1519
  static loadInBackground(src: string, format?: PixelFormat, rotation?: number): Texture;
1516
1520
  static load(src: string, format?: PixelFormat, rotation?: number): Texture;
@@ -1620,7 +1624,7 @@ declare class BrowserCache extends PersistentCache<LocalForage> {
1620
1624
  *
1621
1625
  * `image-size` pulls in `fs`/`path` at module scope, so it can only live in the Node build.
1622
1626
  * Everything MineRender loads is a PNG in practice; GIF and JPEG are handled as a courtesy and
1623
- * anything else falls through to an empty result (callers treat that as 0x0).
1627
+ * anything else falls through to an empty result, which ImageLoader rejects.
1624
1628
  */
1625
1629
  declare function probeImageSize(data: Uint8Array): ImageSizeInfo;
1626
1630
 
@@ -912,29 +912,41 @@ var ImageLoader = class _ImageLoader {
912
912
  static async loadAsync(src) {
913
913
  return new Promise((resolve, reject) => {
914
914
  const image = this._createImage();
915
- image.src = src;
916
915
  image.onload = () => resolve(image);
917
- image.onerror = (err) => reject(err);
916
+ image.onerror = (err) => reject(err instanceof Error ? err : new Error("Failed to decode image"));
917
+ image.src = src;
918
918
  });
919
919
  }
920
920
  static loadElement(src, onload, onerr) {
921
921
  const image = this._createImage();
922
- image.src = src;
923
922
  if (onload)
924
923
  image.onload = onload;
925
924
  if (onerr)
926
925
  image.onerror = onerr;
926
+ image.src = src;
927
927
  return image;
928
928
  }
929
929
  static async loadData(src) {
930
930
  console.debug(p2, "loadData", src);
931
- return await this.infoToData(await this.getInfo(src));
931
+ const image = await this.loadCanvasData(src);
932
+ return image.data.getImageData(0, 0, image.width, image.height);
932
933
  }
933
934
  static async loadCanvasData(src) {
934
- return await this.infoToCanvasData(await this.getInfo(src));
935
+ const keyStr = serializeImageKey({ src });
936
+ const info = this.getInfo(src);
937
+ const cached = Caching.rawImageCache.getIfPresent(keyStr);
938
+ try {
939
+ return await this.infoToCanvasData(await info);
940
+ } catch (err) {
941
+ if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
942
+ Caching.rawImageCache.invalidate(keyStr);
943
+ }
944
+ throw err;
945
+ }
935
946
  }
936
947
  static async infoToCanvasData(info) {
937
- const image = await _ImageLoader.loadAsync(info.src);
948
+ const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
949
+ const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
938
950
  const canvas = createCanvas(info.width, info.height);
939
951
  const context = canvas.getContext("2d");
940
952
  context.drawImage(image, 0, 0);
@@ -945,11 +957,8 @@ var ImageLoader = class _ImageLoader {
945
957
  };
946
958
  }
947
959
  static async infoToData(info) {
948
- const image = await _ImageLoader.loadAsync(info.src);
949
- const canvas = createCanvas(info.width, info.height);
950
- const context = canvas.getContext("2d");
951
- context.drawImage(image, 0, 0);
952
- return context.getImageData(0, 0, canvas.width, canvas.height);
960
+ const image = await this.infoToCanvasData(info);
961
+ return image.data.getImageData(0, 0, image.width, image.height);
953
962
  }
954
963
  static async getData(src) {
955
964
  const keyStr = serializeImageKey({ src });
@@ -964,13 +973,16 @@ var ImageLoader = class _ImageLoader {
964
973
  });
965
974
  }
966
975
  static async processResponse(response) {
967
- const src = response.config.url;
976
+ const src = response.config?.url;
968
977
  const data = import_buffer.Buffer.from(response.data);
969
978
  const { width, height, type } = Env.provider.imageSize(data);
979
+ if (!width || !height) {
980
+ throw new Error("Invalid or unsupported image dimensions");
981
+ }
970
982
  return {
971
983
  src,
972
- width: width || 0,
973
- height: height || 0,
984
+ width,
985
+ height,
974
986
  type,
975
987
  data
976
988
  };
@@ -987,15 +999,7 @@ var ImageLoader = class _ImageLoader {
987
999
  return Requests.genericRequest({
988
1000
  url: src,
989
1001
  responseType: "arraybuffer"
990
- }).then(this.processResponse).catch((err) => {
991
- return {
992
- src,
993
- width: 0,
994
- height: 0,
995
- type: void 0,
996
- data: import_buffer.Buffer.from([0])
997
- };
998
- });
1002
+ }).then(this.processResponse);
999
1003
  }
1000
1004
  static async getInfo(src) {
1001
1005
  const keyStr = serializeImageKey({ src });
@@ -1017,16 +1021,22 @@ var ImageLoader = class _ImageLoader {
1017
1021
 
1018
1022
  // src/texture/TextureLoader.ts
1019
1023
  var TextureLoader = class {
1024
+ /** Reports whether image loading failed, so cached textures can be retried. */
1025
+ static hasFailed(texture) {
1026
+ return this.failedTextures.has(texture);
1027
+ }
1020
1028
  static createTexture() {
1021
1029
  return new import_three4.Texture();
1022
1030
  }
1023
1031
  static loadInBackground(src, format = import_three4.RGBAFormat, rotation = 0) {
1024
1032
  const texture = this.createTexture();
1025
1033
  texture.colorSpace = THREE.SRGBColorSpace;
1026
- const image = ImageLoader.loadElement(src);
1027
- image.onload = function() {
1034
+ const image = ImageLoader.loadElement(src, () => {
1028
1035
  texture.needsUpdate = true;
1029
- };
1036
+ }, (error) => {
1037
+ this.failedTextures.add(texture);
1038
+ console.warn("Failed to load texture", src, error);
1039
+ });
1030
1040
  texture.image = image;
1031
1041
  texture.format = format;
1032
1042
  texture.rotation = rotation;
@@ -1036,14 +1046,18 @@ var TextureLoader = class {
1036
1046
  const texture = new import_three4.Texture();
1037
1047
  texture.colorSpace = THREE.SRGBColorSpace;
1038
1048
  ImageLoader.getData(src).then((image) => {
1039
- texture.needsUpdate = true;
1040
1049
  texture.image = image;
1050
+ texture.needsUpdate = true;
1051
+ }).catch((error) => {
1052
+ this.failedTextures.add(texture);
1053
+ console.warn("Failed to load texture", src, error);
1041
1054
  });
1042
1055
  texture.format = format;
1043
1056
  texture.rotation = rotation;
1044
1057
  return Textures.initTextureProps(texture);
1045
1058
  }
1046
1059
  };
1060
+ TextureLoader.failedTextures = /* @__PURE__ */ new WeakSet();
1047
1061
 
1048
1062
  // src/texture/Textures.ts
1049
1063
  var THREE2 = __toESM(require("three"));
@@ -1067,6 +1081,10 @@ var Textures = class _Textures {
1067
1081
  }
1068
1082
  static getImage(key) {
1069
1083
  const keyStr = serializeTextureKey(key);
1084
+ const cached = Caching.textureCache.peek(keyStr);
1085
+ if (cached && TextureLoader.hasFailed(cached)) {
1086
+ Caching.textureCache.invalidate(keyStr);
1087
+ }
1070
1088
  return Caching.textureCache.get(keyStr, (k) => {
1071
1089
  return _Textures.createImage(key);
1072
1090
  });
@@ -1211,6 +1229,10 @@ var _Materials = class _Materials {
1211
1229
  }
1212
1230
  static getImage(key) {
1213
1231
  const keyStr = serializeMaterialKey(key);
1232
+ const map = Caching.materialCache.peek(keyStr)?.map;
1233
+ if (map && TextureLoader.hasFailed(map)) {
1234
+ Caching.materialCache.invalidate(keyStr);
1235
+ }
1214
1236
  return Caching.materialCache.get(keyStr, (k) => {
1215
1237
  return _Materials.createImage(key);
1216
1238
  });
@@ -1868,11 +1890,21 @@ var ModelTextures = class {
1868
1890
  return this._persistentMetaCache ?? (this._persistentMetaCache = PersistentCache.open("minerender-texturemeta"));
1869
1891
  }
1870
1892
  static async get(key) {
1871
- const asset = await this.preload(key);
1872
- if (asset) {
1873
- return ImageLoader.infoToCanvasData(asset);
1893
+ const keyStr = key.serialize();
1894
+ const pending = this.preload(key);
1895
+ const cached = Caching.textureAssetCache.getIfPresent(keyStr);
1896
+ try {
1897
+ const asset = await pending;
1898
+ if (asset) {
1899
+ return await ImageLoader.infoToCanvasData(asset);
1900
+ }
1901
+ return void 0;
1902
+ } catch (err) {
1903
+ if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
1904
+ Caching.textureAssetCache.invalidate(keyStr);
1905
+ }
1906
+ throw err;
1874
1907
  }
1875
- return void 0;
1876
1908
  }
1877
1909
  static async preload(key) {
1878
1910
  const keyStr = key.serialize();
@@ -3063,8 +3095,8 @@ var EntityObject = class extends SceneObject {
3063
3095
  dispose() {
3064
3096
  super.dispose();
3065
3097
  }
3066
- async loadTextures() {
3067
- this.imageData = await ModelTextures.get(new AssetKey(
3098
+ get textureKey() {
3099
+ return new AssetKey(
3068
3100
  this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
3069
3101
  this.entity.key.path,
3070
3102
  //TODO: texture may differ from entity name; most of them are in subdirectories for multiple variants etc.
@@ -3072,7 +3104,10 @@ var EntityObject = class extends SceneObject {
3072
3104
  "entity",
3073
3105
  "assets",
3074
3106
  ".png"
3075
- ));
3107
+ );
3108
+ }
3109
+ async loadTextures() {
3110
+ this.imageData = await ModelTextures.get(this.textureKey);
3076
3111
  }
3077
3112
  //TODO: abstract this, since extracted model data can be used for entities, blocks, players
3078
3113
  createMeshes(force = false) {
@@ -3105,20 +3140,23 @@ var EntityObject = class extends SceneObject {
3105
3140
  this.meshesCreated = true;
3106
3141
  }
3107
3142
  async applyTextures() {
3108
- const textureAsset = await ModelTextures.preload(new AssetKey(
3109
- this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
3110
- this.entity.key.path,
3111
- "textures",
3112
- "entity",
3113
- "assets",
3114
- ".png"
3115
- ));
3116
- if (textureAsset) {
3117
- let mat = Materials.getImage({ texture: { src: textureAsset.src } });
3118
- this.iterateAllMeshes((mesh) => {
3119
- mesh.material = mat;
3120
- });
3121
- }
3143
+ const assetKeyStr = this.textureKey.serialize();
3144
+ const keyStr = `entity:${assetKeyStr}`;
3145
+ let mat = Caching.materialCache.getIfPresent(keyStr);
3146
+ if (!mat) {
3147
+ const pending = this.loadTextures();
3148
+ const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
3149
+ await pending;
3150
+ if (!this.imageData) return;
3151
+ const canvas = this.imageData.data.canvas;
3152
+ const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
3153
+ mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
3154
+ this.imageData = void 0;
3155
+ }
3156
+ this.iterateAllMeshes((mesh) => {
3157
+ mesh.material = mat;
3158
+ });
3159
+ this.notifyDirty();
3122
3160
  }
3123
3161
  };
3124
3162
  EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, {});