minerender 2.1.0-alpha.6 → 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.
@@ -385,8 +385,8 @@ var Caching = class {
385
385
  /**
386
386
  * Clears every cache and stops its expiry timer.
387
387
  *
388
- * loading-cache reschedules that timer indefinitely, so a Node process will not exit until
389
- * this is called - see {@link shutdown}.
388
+ * Expiry timers are unref'd in Node, so idle processes can exit without this cleanup.
389
+ * Use {@link shutdown} to also stop requests and the shared Ticker.
390
390
  */
391
391
  static end() {
392
392
  for (const cache of this.all) {
@@ -634,13 +634,14 @@ var _Requests = class _Requests {
634
634
  }
635
635
  static get queueSizes() {
636
636
  return {
637
- generic: this.genericQueue.size,
638
- mcAsset: this.mcAssetRequestQueue.size
637
+ generic: this.genericQueue.size + this.genericQueue.activeSize,
638
+ mcAsset: this.mcAssetRequestQueue.size + this.mcAssetRequestQueue.activeSize
639
639
  };
640
640
  }
641
641
  /**
642
- * Stops both queue timers. jobqu reschedules itself indefinitely, so a Node process will not
643
- * exit until this is called - see {@link shutdown}.
642
+ * Permanently stops both queues and rejects waiting and future requests.
643
+ * Requests already running can finish. Idle queues hold no timer, so Node can exit without
644
+ * this cleanup. See {@link shutdown}.
644
645
  */
645
646
  static end() {
646
647
  this.genericQueue.end();
@@ -720,29 +721,41 @@ var ImageLoader = class _ImageLoader {
720
721
  static async loadAsync(src) {
721
722
  return new Promise((resolve, reject) => {
722
723
  const image = this._createImage();
723
- image.src = src;
724
724
  image.onload = () => resolve(image);
725
- image.onerror = (err) => reject(err);
725
+ image.onerror = (err) => reject(err instanceof Error ? err : new Error("Failed to decode image"));
726
+ image.src = src;
726
727
  });
727
728
  }
728
729
  static loadElement(src, onload, onerr) {
729
730
  const image = this._createImage();
730
- image.src = src;
731
731
  if (onload)
732
732
  image.onload = onload;
733
733
  if (onerr)
734
734
  image.onerror = onerr;
735
+ image.src = src;
735
736
  return image;
736
737
  }
737
738
  static async loadData(src) {
738
739
  console.debug(p2, "loadData", src);
739
- return await this.infoToData(await this.getInfo(src));
740
+ const image = await this.loadCanvasData(src);
741
+ return image.data.getImageData(0, 0, image.width, image.height);
740
742
  }
741
743
  static async loadCanvasData(src) {
742
- return await this.infoToCanvasData(await this.getInfo(src));
744
+ const keyStr = serializeImageKey({ src });
745
+ const info = this.getInfo(src);
746
+ const cached = Caching.rawImageCache.getIfPresent(keyStr);
747
+ try {
748
+ return await this.infoToCanvasData(await info);
749
+ } catch (err) {
750
+ if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
751
+ Caching.rawImageCache.invalidate(keyStr);
752
+ }
753
+ throw err;
754
+ }
743
755
  }
744
756
  static async infoToCanvasData(info) {
745
- const image = await _ImageLoader.loadAsync(info.src);
757
+ const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
758
+ const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
746
759
  const canvas = createCanvas2(info.width, info.height);
747
760
  const context = canvas.getContext("2d");
748
761
  context.drawImage(image, 0, 0);
@@ -753,11 +766,8 @@ var ImageLoader = class _ImageLoader {
753
766
  };
754
767
  }
755
768
  static async infoToData(info) {
756
- const image = await _ImageLoader.loadAsync(info.src);
757
- const canvas = createCanvas2(info.width, info.height);
758
- const context = canvas.getContext("2d");
759
- context.drawImage(image, 0, 0);
760
- return context.getImageData(0, 0, canvas.width, canvas.height);
769
+ const image = await this.infoToCanvasData(info);
770
+ return image.data.getImageData(0, 0, image.width, image.height);
761
771
  }
762
772
  static async getData(src) {
763
773
  const keyStr = serializeImageKey({ src });
@@ -772,13 +782,17 @@ var ImageLoader = class _ImageLoader {
772
782
  });
773
783
  }
774
784
  static async processResponse(response) {
775
- const src = response.config.url;
785
+ var _a;
786
+ const src = (_a = response.config) == null ? void 0 : _a.url;
776
787
  const data = Buffer2.from(response.data);
777
788
  const { width, height, type } = Env.provider.imageSize(data);
789
+ if (!width || !height) {
790
+ throw new Error("Invalid or unsupported image dimensions");
791
+ }
778
792
  return {
779
793
  src,
780
- width: width || 0,
781
- height: height || 0,
794
+ width,
795
+ height,
782
796
  type,
783
797
  data
784
798
  };
@@ -795,15 +809,7 @@ var ImageLoader = class _ImageLoader {
795
809
  return Requests.genericRequest({
796
810
  url: src,
797
811
  responseType: "arraybuffer"
798
- }).then(this.processResponse).catch((err) => {
799
- return {
800
- src,
801
- width: 0,
802
- height: 0,
803
- type: void 0,
804
- data: Buffer2.from([0])
805
- };
806
- });
812
+ }).then(this.processResponse);
807
813
  }
808
814
  static async getInfo(src) {
809
815
  const keyStr = serializeImageKey({ src });
@@ -825,16 +831,22 @@ var ImageLoader = class _ImageLoader {
825
831
 
826
832
  // src/texture/TextureLoader.ts
827
833
  var TextureLoader = class {
834
+ /** Reports whether image loading failed, so cached textures can be retried. */
835
+ static hasFailed(texture) {
836
+ return this.failedTextures.has(texture);
837
+ }
828
838
  static createTexture() {
829
839
  return new Texture();
830
840
  }
831
841
  static loadInBackground(src, format = RGBAFormat, rotation = 0) {
832
842
  const texture = this.createTexture();
833
843
  texture.colorSpace = THREE.SRGBColorSpace;
834
- const image = ImageLoader.loadElement(src);
835
- image.onload = function() {
844
+ const image = ImageLoader.loadElement(src, () => {
836
845
  texture.needsUpdate = true;
837
- };
846
+ }, (error) => {
847
+ this.failedTextures.add(texture);
848
+ console.warn("Failed to load texture", src, error);
849
+ });
838
850
  texture.image = image;
839
851
  texture.format = format;
840
852
  texture.rotation = rotation;
@@ -844,14 +856,18 @@ var TextureLoader = class {
844
856
  const texture = new Texture();
845
857
  texture.colorSpace = THREE.SRGBColorSpace;
846
858
  ImageLoader.getData(src).then((image) => {
847
- texture.needsUpdate = true;
848
859
  texture.image = image;
860
+ texture.needsUpdate = true;
861
+ }).catch((error) => {
862
+ this.failedTextures.add(texture);
863
+ console.warn("Failed to load texture", src, error);
849
864
  });
850
865
  texture.format = format;
851
866
  texture.rotation = rotation;
852
867
  return Textures.initTextureProps(texture);
853
868
  }
854
869
  };
870
+ TextureLoader.failedTextures = /* @__PURE__ */ new WeakSet();
855
871
 
856
872
  // src/texture/Textures.ts
857
873
  import * as THREE2 from "three";
@@ -875,6 +891,10 @@ var Textures = class _Textures {
875
891
  }
876
892
  static getImage(key) {
877
893
  const keyStr = serializeTextureKey(key);
894
+ const cached = Caching.textureCache.peek(keyStr);
895
+ if (cached && TextureLoader.hasFailed(cached)) {
896
+ Caching.textureCache.invalidate(keyStr);
897
+ }
878
898
  return Caching.textureCache.get(keyStr, (k) => {
879
899
  return _Textures.createImage(key);
880
900
  });
@@ -1018,7 +1038,12 @@ var _Materials = class _Materials {
1018
1038
  return this.createBasicCanvasMaterial(canvas, transparent, shade);
1019
1039
  }
1020
1040
  static getImage(key) {
1041
+ var _a;
1021
1042
  const keyStr = serializeMaterialKey(key);
1043
+ const map = (_a = Caching.materialCache.peek(keyStr)) == null ? void 0 : _a.map;
1044
+ if (map && TextureLoader.hasFailed(map)) {
1045
+ Caching.materialCache.invalidate(keyStr);
1046
+ }
1022
1047
  return Caching.materialCache.get(keyStr, (k) => {
1023
1048
  return _Materials.createImage(key);
1024
1049
  });
@@ -1676,11 +1701,21 @@ var ModelTextures = class {
1676
1701
  return this._persistentMetaCache ??= PersistentCache.open("minerender-texturemeta");
1677
1702
  }
1678
1703
  static async get(key) {
1679
- const asset = await this.preload(key);
1680
- if (asset) {
1681
- return ImageLoader.infoToCanvasData(asset);
1704
+ const keyStr = key.serialize();
1705
+ const pending = this.preload(key);
1706
+ const cached = Caching.textureAssetCache.getIfPresent(keyStr);
1707
+ try {
1708
+ const asset = await pending;
1709
+ if (asset) {
1710
+ return await ImageLoader.infoToCanvasData(asset);
1711
+ }
1712
+ return void 0;
1713
+ } catch (err) {
1714
+ if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
1715
+ Caching.textureAssetCache.invalidate(keyStr);
1716
+ }
1717
+ throw err;
1682
1718
  }
1683
- return void 0;
1684
1719
  }
1685
1720
  static async preload(key) {
1686
1721
  const keyStr = key.serialize();
@@ -2874,9 +2909,9 @@ var EntityObject = class extends SceneObject {
2874
2909
  dispose() {
2875
2910
  super.dispose();
2876
2911
  }
2877
- async loadTextures() {
2912
+ get textureKey() {
2878
2913
  var _a;
2879
- this.imageData = await ModelTextures.get(new AssetKey(
2914
+ return new AssetKey(
2880
2915
  ((_a = this.entity.key) == null ? void 0 : _a.namespace) ?? DEFAULT_NAMESPACE,
2881
2916
  this.entity.key.path,
2882
2917
  //TODO: texture may differ from entity name; most of them are in subdirectories for multiple variants etc.
@@ -2884,7 +2919,10 @@ var EntityObject = class extends SceneObject {
2884
2919
  "entity",
2885
2920
  "assets",
2886
2921
  ".png"
2887
- ));
2922
+ );
2923
+ }
2924
+ async loadTextures() {
2925
+ this.imageData = await ModelTextures.get(this.textureKey);
2888
2926
  }
2889
2927
  //TODO: abstract this, since extracted model data can be used for entities, blocks, players
2890
2928
  createMeshes(force = false) {
@@ -2917,21 +2955,23 @@ var EntityObject = class extends SceneObject {
2917
2955
  this.meshesCreated = true;
2918
2956
  }
2919
2957
  async applyTextures() {
2920
- var _a;
2921
- const textureAsset = await ModelTextures.preload(new AssetKey(
2922
- ((_a = this.entity.key) == null ? void 0 : _a.namespace) ?? DEFAULT_NAMESPACE,
2923
- this.entity.key.path,
2924
- "textures",
2925
- "entity",
2926
- "assets",
2927
- ".png"
2928
- ));
2929
- if (textureAsset) {
2930
- let mat = Materials.getImage({ texture: { src: textureAsset.src } });
2931
- this.iterateAllMeshes((mesh) => {
2932
- mesh.material = mat;
2933
- });
2934
- }
2958
+ const assetKeyStr = this.textureKey.serialize();
2959
+ const keyStr = `entity:${assetKeyStr}`;
2960
+ let mat = Caching.materialCache.getIfPresent(keyStr);
2961
+ if (!mat) {
2962
+ const pending = this.loadTextures();
2963
+ const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
2964
+ await pending;
2965
+ if (!this.imageData) return;
2966
+ const canvas = this.imageData.data.canvas;
2967
+ const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
2968
+ mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
2969
+ this.imageData = void 0;
2970
+ }
2971
+ this.iterateAllMeshes((mesh) => {
2972
+ mesh.material = mat;
2973
+ });
2974
+ this.notifyDirty();
2935
2975
  }
2936
2976
  };
2937
2977
  EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, {});