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.
@@ -422,8 +422,8 @@ var Caching = class {
422
422
  /**
423
423
  * Clears every cache and stops its expiry timer.
424
424
  *
425
- * loading-cache reschedules that timer indefinitely, so a Node process will not exit until
426
- * this is called - see {@link shutdown}.
425
+ * Expiry timers are unref'd in Node, so idle processes can exit without this cleanup.
426
+ * Use {@link shutdown} to also stop requests and the shared Ticker.
427
427
  */
428
428
  static end() {
429
429
  for (const cache of this.all) {
@@ -671,13 +671,14 @@ var _Requests = class _Requests {
671
671
  }
672
672
  static get queueSizes() {
673
673
  return {
674
- generic: this.genericQueue.size,
675
- mcAsset: this.mcAssetRequestQueue.size
674
+ generic: this.genericQueue.size + this.genericQueue.activeSize,
675
+ mcAsset: this.mcAssetRequestQueue.size + this.mcAssetRequestQueue.activeSize
676
676
  };
677
677
  }
678
678
  /**
679
- * Stops both queue timers. jobqu reschedules itself indefinitely, so a Node process will not
680
- * exit until this is called - see {@link shutdown}.
679
+ * Permanently stops both queues and rejects waiting and future requests.
680
+ * Requests already running can finish. Idle queues hold no timer, so Node can exit without
681
+ * this cleanup. See {@link shutdown}.
681
682
  */
682
683
  static end() {
683
684
  this.genericQueue.end();
@@ -757,29 +758,41 @@ var ImageLoader = class _ImageLoader {
757
758
  static async loadAsync(src) {
758
759
  return new Promise((resolve, reject) => {
759
760
  const image = this._createImage();
760
- image.src = src;
761
761
  image.onload = () => resolve(image);
762
- image.onerror = (err) => reject(err);
762
+ image.onerror = (err) => reject(err instanceof Error ? err : new Error("Failed to decode image"));
763
+ image.src = src;
763
764
  });
764
765
  }
765
766
  static loadElement(src, onload, onerr) {
766
767
  const image = this._createImage();
767
- image.src = src;
768
768
  if (onload)
769
769
  image.onload = onload;
770
770
  if (onerr)
771
771
  image.onerror = onerr;
772
+ image.src = src;
772
773
  return image;
773
774
  }
774
775
  static async loadData(src) {
775
776
  console.debug(p2, "loadData", src);
776
- return await this.infoToData(await this.getInfo(src));
777
+ const image = await this.loadCanvasData(src);
778
+ return image.data.getImageData(0, 0, image.width, image.height);
777
779
  }
778
780
  static async loadCanvasData(src) {
779
- return await this.infoToCanvasData(await this.getInfo(src));
781
+ const keyStr = serializeImageKey({ src });
782
+ const info = this.getInfo(src);
783
+ const cached = Caching.rawImageCache.getIfPresent(keyStr);
784
+ try {
785
+ return await this.infoToCanvasData(await info);
786
+ } catch (err) {
787
+ if (cached && Caching.rawImageCache.getIfPresent(keyStr) === cached) {
788
+ Caching.rawImageCache.invalidate(keyStr);
789
+ }
790
+ throw err;
791
+ }
780
792
  }
781
793
  static async infoToCanvasData(info) {
782
- const image = await _ImageLoader.loadAsync(info.src);
794
+ const type = info.type === "jpg" ? "jpeg" : info.type ?? "png";
795
+ const image = await _ImageLoader.loadAsync(`data:image/${type};base64,${info.data.toString("base64")}`);
783
796
  const canvas = createCanvas(info.width, info.height);
784
797
  const context = canvas.getContext("2d");
785
798
  context.drawImage(image, 0, 0);
@@ -790,11 +803,8 @@ var ImageLoader = class _ImageLoader {
790
803
  };
791
804
  }
792
805
  static async infoToData(info) {
793
- const image = await _ImageLoader.loadAsync(info.src);
794
- const canvas = createCanvas(info.width, info.height);
795
- const context = canvas.getContext("2d");
796
- context.drawImage(image, 0, 0);
797
- return context.getImageData(0, 0, canvas.width, canvas.height);
806
+ const image = await this.infoToCanvasData(info);
807
+ return image.data.getImageData(0, 0, image.width, image.height);
798
808
  }
799
809
  static async getData(src) {
800
810
  const keyStr = serializeImageKey({ src });
@@ -809,13 +819,16 @@ var ImageLoader = class _ImageLoader {
809
819
  });
810
820
  }
811
821
  static async processResponse(response) {
812
- const src = response.config.url;
822
+ const src = response.config?.url;
813
823
  const data = Buffer2.from(response.data);
814
824
  const { width, height, type } = Env.provider.imageSize(data);
825
+ if (!width || !height) {
826
+ throw new Error("Invalid or unsupported image dimensions");
827
+ }
815
828
  return {
816
829
  src,
817
- width: width || 0,
818
- height: height || 0,
830
+ width,
831
+ height,
819
832
  type,
820
833
  data
821
834
  };
@@ -832,15 +845,7 @@ var ImageLoader = class _ImageLoader {
832
845
  return Requests.genericRequest({
833
846
  url: src,
834
847
  responseType: "arraybuffer"
835
- }).then(this.processResponse).catch((err) => {
836
- return {
837
- src,
838
- width: 0,
839
- height: 0,
840
- type: void 0,
841
- data: Buffer2.from([0])
842
- };
843
- });
848
+ }).then(this.processResponse);
844
849
  }
845
850
  static async getInfo(src) {
846
851
  const keyStr = serializeImageKey({ src });
@@ -862,16 +867,22 @@ var ImageLoader = class _ImageLoader {
862
867
 
863
868
  // src/texture/TextureLoader.ts
864
869
  var TextureLoader = class {
870
+ /** Reports whether image loading failed, so cached textures can be retried. */
871
+ static hasFailed(texture) {
872
+ return this.failedTextures.has(texture);
873
+ }
865
874
  static createTexture() {
866
875
  return new Texture();
867
876
  }
868
877
  static loadInBackground(src, format = RGBAFormat, rotation = 0) {
869
878
  const texture = this.createTexture();
870
879
  texture.colorSpace = THREE.SRGBColorSpace;
871
- const image = ImageLoader.loadElement(src);
872
- image.onload = function() {
880
+ const image = ImageLoader.loadElement(src, () => {
873
881
  texture.needsUpdate = true;
874
- };
882
+ }, (error) => {
883
+ this.failedTextures.add(texture);
884
+ console.warn("Failed to load texture", src, error);
885
+ });
875
886
  texture.image = image;
876
887
  texture.format = format;
877
888
  texture.rotation = rotation;
@@ -881,14 +892,18 @@ var TextureLoader = class {
881
892
  const texture = new Texture();
882
893
  texture.colorSpace = THREE.SRGBColorSpace;
883
894
  ImageLoader.getData(src).then((image) => {
884
- texture.needsUpdate = true;
885
895
  texture.image = image;
896
+ texture.needsUpdate = true;
897
+ }).catch((error) => {
898
+ this.failedTextures.add(texture);
899
+ console.warn("Failed to load texture", src, error);
886
900
  });
887
901
  texture.format = format;
888
902
  texture.rotation = rotation;
889
903
  return Textures.initTextureProps(texture);
890
904
  }
891
905
  };
906
+ TextureLoader.failedTextures = /* @__PURE__ */ new WeakSet();
892
907
 
893
908
  // src/texture/Textures.ts
894
909
  import * as THREE2 from "three";
@@ -912,6 +927,10 @@ var Textures = class _Textures {
912
927
  }
913
928
  static getImage(key) {
914
929
  const keyStr = serializeTextureKey(key);
930
+ const cached = Caching.textureCache.peek(keyStr);
931
+ if (cached && TextureLoader.hasFailed(cached)) {
932
+ Caching.textureCache.invalidate(keyStr);
933
+ }
915
934
  return Caching.textureCache.get(keyStr, (k) => {
916
935
  return _Textures.createImage(key);
917
936
  });
@@ -1056,6 +1075,10 @@ var _Materials = class _Materials {
1056
1075
  }
1057
1076
  static getImage(key) {
1058
1077
  const keyStr = serializeMaterialKey(key);
1078
+ const map = Caching.materialCache.peek(keyStr)?.map;
1079
+ if (map && TextureLoader.hasFailed(map)) {
1080
+ Caching.materialCache.invalidate(keyStr);
1081
+ }
1059
1082
  return Caching.materialCache.get(keyStr, (k) => {
1060
1083
  return _Materials.createImage(key);
1061
1084
  });
@@ -1713,11 +1736,21 @@ var ModelTextures = class {
1713
1736
  return this._persistentMetaCache ?? (this._persistentMetaCache = PersistentCache.open("minerender-texturemeta"));
1714
1737
  }
1715
1738
  static async get(key) {
1716
- const asset = await this.preload(key);
1717
- if (asset) {
1718
- return ImageLoader.infoToCanvasData(asset);
1739
+ const keyStr = key.serialize();
1740
+ const pending = this.preload(key);
1741
+ const cached = Caching.textureAssetCache.getIfPresent(keyStr);
1742
+ try {
1743
+ const asset = await pending;
1744
+ if (asset) {
1745
+ return await ImageLoader.infoToCanvasData(asset);
1746
+ }
1747
+ return void 0;
1748
+ } catch (err) {
1749
+ if (cached && Caching.textureAssetCache.getIfPresent(keyStr) === cached) {
1750
+ Caching.textureAssetCache.invalidate(keyStr);
1751
+ }
1752
+ throw err;
1719
1753
  }
1720
- return void 0;
1721
1754
  }
1722
1755
  static async preload(key) {
1723
1756
  const keyStr = key.serialize();
@@ -2908,8 +2941,8 @@ var EntityObject = class extends SceneObject {
2908
2941
  dispose() {
2909
2942
  super.dispose();
2910
2943
  }
2911
- async loadTextures() {
2912
- this.imageData = await ModelTextures.get(new AssetKey(
2944
+ get textureKey() {
2945
+ return new AssetKey(
2913
2946
  this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
2914
2947
  this.entity.key.path,
2915
2948
  //TODO: texture may differ from entity name; most of them are in subdirectories for multiple variants etc.
@@ -2917,7 +2950,10 @@ var EntityObject = class extends SceneObject {
2917
2950
  "entity",
2918
2951
  "assets",
2919
2952
  ".png"
2920
- ));
2953
+ );
2954
+ }
2955
+ async loadTextures() {
2956
+ this.imageData = await ModelTextures.get(this.textureKey);
2921
2957
  }
2922
2958
  //TODO: abstract this, since extracted model data can be used for entities, blocks, players
2923
2959
  createMeshes(force = false) {
@@ -2950,20 +2986,23 @@ var EntityObject = class extends SceneObject {
2950
2986
  this.meshesCreated = true;
2951
2987
  }
2952
2988
  async applyTextures() {
2953
- const textureAsset = await ModelTextures.preload(new AssetKey(
2954
- this.entity.key?.namespace ?? DEFAULT_NAMESPACE,
2955
- this.entity.key.path,
2956
- "textures",
2957
- "entity",
2958
- "assets",
2959
- ".png"
2960
- ));
2961
- if (textureAsset) {
2962
- let mat = Materials.getImage({ texture: { src: textureAsset.src } });
2963
- this.iterateAllMeshes((mesh) => {
2964
- mesh.material = mat;
2965
- });
2966
- }
2989
+ const assetKeyStr = this.textureKey.serialize();
2990
+ const keyStr = `entity:${assetKeyStr}`;
2991
+ let mat = Caching.materialCache.getIfPresent(keyStr);
2992
+ if (!mat) {
2993
+ const pending = this.loadTextures();
2994
+ const cachedAsset = Caching.textureAssetCache.getIfPresent(assetKeyStr);
2995
+ await pending;
2996
+ if (!this.imageData) return;
2997
+ const canvas = this.imageData.data.canvas;
2998
+ const createMaterial = () => Materials.createBasicCanvasMaterial(canvas);
2999
+ mat = cachedAsset && Caching.textureAssetCache.getIfPresent(assetKeyStr) === cachedAsset ? Caching.materialCache.get(keyStr, createMaterial) : createMaterial();
3000
+ this.imageData = void 0;
3001
+ }
3002
+ this.iterateAllMeshes((mesh) => {
3003
+ mesh.material = mat;
3004
+ });
3005
+ this.notifyDirty();
2967
3006
  }
2968
3007
  };
2969
3008
  EntityObject.DEFAULT_OPTIONS = (0, import_ts_deepmerge4.default)({}, SceneObject.DEFAULT_OPTIONS, {});