@@ -23,6 +23,8 @@ import { GameType } from "../minecraft/WorldLevelDat";
2323
2424import { IGeneratorOptions } from "./ProjectInfoSet" ;
2525
26+ export const MaxWorldRecordsToProcess = 3000000 ; // very crudely, this equates to about 100K chunks
27+
2628export enum WorldDataInfoGeneratorTest {
2729 unexpectedCommandInMCFunction = 101 ,
2830 unexpectedCommandInCommandBlock = 102 ,
@@ -40,6 +42,7 @@ export enum WorldDataInfoGeneratorTest {
4042 subchunklessChunks = 127 ,
4143 chunks = 128 ,
4244 commandIsFromOlderMinecraftVersion = 212 ,
45+ couldNotProcessWorld = 216 ,
4346 errorProcessingWorld = 400 ,
4447 unexpectedError = 401 ,
4548}
@@ -67,6 +70,9 @@ export default class WorldDataInfoGenerator implements IProjectInfoItemGenerator
6770 WorldDataInfoGeneratorTest . subchunklessChunks
6871 ) ;
6972
73+ info . completedWorldDataProcessing =
74+ infoSet . getCount ( "WORLDDATA" , WorldDataInfoGeneratorTest . couldNotProcessWorld ) === 0 ;
75+
7076 info . worldLoadErrors = infoSet . getCount ( "WORLDDATA" , WorldDataInfoGeneratorTest . errorProcessingWorld ) ;
7177
7278 const levelItems = infoSet . getItems ( this . id , WorldDataInfoGeneratorTest . levelDat ) ;
@@ -360,7 +366,9 @@ export default class WorldDataInfoGenerator implements IProjectInfoItemGenerator
360366
361367 await mcworld . loadMetaFiles ( false ) ;
362368
363- await mcworld . loadLevelDb ( false ) ;
369+ let didProcessWorldData = await mcworld . loadLevelDb ( false , {
370+ maxNumberOfRecordsToProcess : MaxWorldRecordsToProcess ,
371+ } ) ;
364372
365373 if (
366374 mcworld . isInErrorState &&
@@ -384,6 +392,24 @@ export default class WorldDataInfoGenerator implements IProjectInfoItemGenerator
384392 )
385393 ) ;
386394 }
395+
396+ didProcessWorldData = false ;
397+ }
398+
399+ if ( ! didProcessWorldData ) {
400+ items . push (
401+ new ProjectInfoItem (
402+ InfoItemType . info ,
403+ this . id ,
404+ WorldDataInfoGeneratorTest . couldNotProcessWorld ,
405+ ProjectInfoUtilities . getTitleFromEnum (
406+ WorldDataInfoGeneratorTest ,
407+ WorldDataInfoGeneratorTest . couldNotProcessWorld
408+ ) ,
409+ projectItem ,
410+ mcworld . name
411+ )
412+ ) ;
387413 }
388414
389415 if (
@@ -458,141 +484,155 @@ export default class WorldDataInfoGenerator implements IProjectInfoItemGenerator
458484 )
459485 ) ;
460486
461- let blockCount = 0 ;
462- let chunkCount = 0 ;
463- let subchunkLessChunkCount = 0 ;
464-
465- // Use the memory-efficient chunk iterator with aggressive data clearing
466- // This prevents heap exhaustion on large worlds by clearing chunk data after processing
467- await mcworld . forEachChunk (
468- async ( chunk , x , z , dimIndex ) => {
469- chunkCount ++ ;
487+ if ( didProcessWorldData ) {
488+ let blockCount = 0 ;
489+ let chunkCount = 0 ;
490+ let subchunkLessChunkCount = 0 ;
470491
471- if ( chunk . subChunks . length <= 0 ) {
472- subchunkLessChunkCount ++ ;
473- }
492+ // Use the memory-efficient chunk iterator with aggressive data clearing
493+ // This prevents heap exhaustion on large worlds by clearing chunk data after processing
494+ await mcworld . forEachChunk (
495+ async ( chunk , x , z , dimIndex ) => {
496+ chunkCount ++ ;
474497
475- const blockActors = chunk . blockActors ;
498+ if ( chunk . subChunks . length <= 0 ) {
499+ subchunkLessChunkCount ++ ;
500+ }
476501
477- for ( let i = 0 ; i < blockActors . length ; i ++ ) {
478- const blockActor = blockActors [ i ] ;
502+ const blockActors = chunk . blockActors ;
479503
480- if ( blockActor . id ) {
481- blockActorsPi . incrementFeature ( blockActor . id ) ;
482- }
504+ for ( let i = 0 ; i < blockActors . length ; i ++ ) {
505+ const blockActor = blockActors [ i ] ;
483506
484- if ( blockActor instanceof CommandBlockActor ) {
485- let cba = blockActor as CommandBlockActor ;
486- if ( cba . version ) {
487- blockActorsPi . spectrumIntFeature ( "Command Version" , cba . version ) ;
507+ if ( blockActor . id ) {
508+ blockActorsPi . incrementFeature ( blockActor . id ) ;
488509 }
489510
490- if ( cba . version && cba . version < this . modernCommandVersion ) {
491- items . push (
492- new ProjectInfoItem (
493- InfoItemType . recommendation ,
494- this . id ,
495- WorldDataInfoGeneratorTest . commandIsFromOlderMinecraftVersion ,
496- "Command '" + cba . command + "' is from an older Minecraft version (" + cba . version + ") " ,
497- projectItem ,
498- "(Command at location " + cba . x + ", " + cba . y + ", " + cba . z + ")" ,
499- undefined ,
500- cba . command
501- )
502- ) ;
503- }
511+ if ( blockActor instanceof CommandBlockActor ) {
512+ let cba = blockActor as CommandBlockActor ;
513+ if ( cba . version ) {
514+ blockActorsPi . spectrumIntFeature ( "Command Version" , cba . version ) ;
515+ }
516+
517+ if ( cba . version && cba . version < this . modernCommandVersion ) {
518+ items . push (
519+ new ProjectInfoItem (
520+ InfoItemType . recommendation ,
521+ this . id ,
522+ WorldDataInfoGeneratorTest . commandIsFromOlderMinecraftVersion ,
523+ "Command '" + cba . command + "' is from an older Minecraft version (" + cba . version + ") " ,
524+ projectItem ,
525+ "(Command at location " + cba . x + ", " + cba . y + ", " + cba . z + ")" ,
526+ undefined ,
527+ cba . command
528+ )
529+ ) ;
530+ }
504531
505- if ( cba . command && cba . command . trim ( ) . length > 2 ) {
506- let command = CommandStructure . parse ( cba . command ) ;
532+ if ( cba . command && cba . command . trim ( ) . length > 2 ) {
533+ let command = CommandStructure . parse ( cba . command ) ;
534+
535+ if ( CommandRegistry . isMinecraftBuiltInCommand ( command . fullName ) ) {
536+ if ( this . performAddOnValidations && CommandRegistry . isAddOnBlockedCommand ( command . fullName ) ) {
537+ items . push (
538+ new ProjectInfoItem (
539+ InfoItemType . warning ,
540+ this . id ,
541+ WorldDataInfoGeneratorTest . containsWorldImpactingCommand ,
542+ "Contains command '" +
543+ command . fullName +
544+ "' which is impacts the state of the entire world, and generally shouldn't be used in an add-on" ,
545+ projectItem ,
546+ command . fullName ,
547+ undefined ,
548+ cba . command
549+ )
550+ ) ;
551+ }
507552
508- if ( CommandRegistry . isMinecraftBuiltInCommand ( command . fullName ) ) {
509- if ( this . performAddOnValidations && CommandRegistry . isAddOnBlockedCommand ( command . fullName ) ) {
553+ commandsPi . incrementFeature ( command . fullName ) ;
554+
555+ if ( command . fullName === "execute" ) {
556+ let foundRun = false ;
557+ for ( const arg of command . commandArguments ) {
558+ if ( arg === "run" ) {
559+ foundRun = true ;
560+ } else if ( foundRun && CommandRegistry . isMinecraftBuiltInCommand ( arg ) ) {
561+ subCommandsPi . incrementFeature ( arg ) ;
562+ break ;
563+ }
564+ }
565+ }
566+ } else if ( ! this . performAddOnValidations && ! this . performPlatformVersionValidations ) {
510567 items . push (
511568 new ProjectInfoItem (
512- InfoItemType . warning ,
569+ InfoItemType . error ,
513570 this . id ,
514- WorldDataInfoGeneratorTest . containsWorldImpactingCommand ,
515- "Contains command '" +
516- command . fullName +
517- "' which is impacts the state of the entire world, and generally shouldn't be used in an add-on" ,
571+ WorldDataInfoGeneratorTest . unexpectedCommandInCommandBlock ,
572+ "Unexpected command '" + command . fullName + "'" ,
518573 projectItem ,
519574 command . fullName ,
520575 undefined ,
521576 cba . command
522577 )
523578 ) ;
524579 }
525-
526- commandsPi . incrementFeature ( command . fullName ) ;
527-
528- if ( command . fullName === "execute" ) {
529- let foundRun = false ;
530- for ( const arg of command . commandArguments ) {
531- if ( arg === "run" ) {
532- foundRun = true ;
533- } else if ( foundRun && CommandRegistry . isMinecraftBuiltInCommand ( arg ) ) {
534- subCommandsPi . incrementFeature ( arg ) ;
535- break ;
536- }
537- }
538- }
539- } else if ( ! this . performAddOnValidations && ! this . performPlatformVersionValidations ) {
540- items . push (
541- new ProjectInfoItem (
542- InfoItemType . error ,
543- this . id ,
544- WorldDataInfoGeneratorTest . unexpectedCommandInCommandBlock ,
545- "Unexpected command '" + command . fullName + "'" ,
546- projectItem ,
547- command . fullName ,
548- undefined ,
549- cba . command
550- )
551- ) ;
552580 }
553581 }
554582 }
555- }
556583
557- // Use memory-efficient block type counting instead of getBlockList()
558- // This avoids allocating massive arrays of Block objects
559- const blockTypeCounts = chunk . countBlockTypes ( ) ;
584+ // Use memory-efficient block type counting instead of getBlockList()
585+ // This avoids allocating massive arrays of Block objects
586+ const blockTypeCounts = chunk . countBlockTypes ( ) ;
560587
561- for ( const [ typeName , count ] of blockTypeCounts ) {
562- blockCount += count ;
588+ for ( const [ typeName , count ] of blockTypeCounts ) {
589+ blockCount += count ;
563590
564- let type = typeName ;
565- if ( type . indexOf ( ":" ) >= 0 && type . indexOf ( "minecraft:" ) < 0 ) {
566- type = "(custom)" ;
567- }
591+ let type = typeName ;
592+ if ( type . indexOf ( ":" ) >= 0 && type . indexOf ( "minecraft:" ) < 0 ) {
593+ type = "(custom)" ;
594+ }
568595
569- blocksPi . incrementFeature ( type , "count" , count ) ;
570- }
571- } ,
572- {
573- // Always clear parsed/cached data after processing each chunk to prevent OOM.
574- // This is non-destructive - raw LevelKeyValue bytes are preserved, so chunks
575- // can be re-parsed on demand (e.g., when the world map needs them).
576- clearCacheAfterProcess : true ,
577- // Only clear raw LevelKeyValue data when in aggressive cleanup mode (CLI validation).
578- // In browser contexts, we preserve raw data so the world map can re-parse chunks.
579- clearAllAfterProcess : performAggressiveCleanup ,
580- progressCallback : async ( processed , total ) => {
581- // Use worldName captured from outside closure to avoid TypeScript null check issue
582- const worldName = mcworld ?. name ?? "unknown" ;
583- let mess =
584- "World data validation: scanned " +
585- Math . floor ( processed / 1000 ) +
586- "K of " +
587- Math . floor ( total / 1000 ) +
588- "K chunks in " +
589- worldName ;
590- await projectItem . project . creatorTools . notifyStatusUpdate ( mess , StatusTopic . validation ) ;
596+ blocksPi . incrementFeature ( type , "count" , count ) ;
597+ }
591598 } ,
592- }
593- ) ;
594-
595- blocksPi . data = blockCount ;
599+ {
600+ // Always clear parsed/cached data after processing each chunk to prevent OOM.
601+ // This is non-destructive - raw LevelKeyValue bytes are preserved, so chunks
602+ // can be re-parsed on demand (e.g., when the world map needs them).
603+ clearCacheAfterProcess : true ,
604+ // Only clear raw LevelKeyValue data when in aggressive cleanup mode (CLI validation).
605+ // In browser contexts, we preserve raw data so the world map can re-parse chunks.
606+ clearAllAfterProcess : performAggressiveCleanup ,
607+ progressCallback : async ( processed , total ) => {
608+ // Use worldName captured from outside closure to avoid TypeScript null check issue
609+ const worldName = mcworld ?. name ?? "unknown" ;
610+ let mess =
611+ "World data validation: scanned " +
612+ Math . floor ( processed / 1000 ) +
613+ "K of " +
614+ Math . floor ( total / 1000 ) +
615+ "K chunks in " +
616+ worldName ;
617+ await projectItem . project . creatorTools . notifyStatusUpdate ( mess , StatusTopic . validation ) ;
618+ } ,
619+ }
620+ ) ;
621+
622+ items . push (
623+ new ProjectInfoItem (
624+ InfoItemType . info ,
625+ this . id ,
626+ WorldDataInfoGeneratorTest . subchunklessChunks ,
627+ "Subchunkless Chunks" ,
628+ projectItem ,
629+ subchunkLessChunkCount ,
630+ mcworld . name
631+ )
632+ ) ;
633+
634+ blocksPi . data = blockCount ;
635+ }
596636
597637 items . push (
598638 new ProjectInfoItem (
@@ -638,17 +678,6 @@ export default class WorldDataInfoGenerator implements IProjectInfoItemGenerator
638678 mcworld . name
639679 )
640680 ) ;
641- items . push (
642- new ProjectInfoItem (
643- InfoItemType . info ,
644- this . id ,
645- WorldDataInfoGeneratorTest . subchunklessChunks ,
646- "Subchunkless Chunks" ,
647- projectItem ,
648- subchunkLessChunkCount ,
649- mcworld . name
650- )
651- ) ;
652681 }
653682
654683 return items ;
0 commit comments