mojira.dev

YUNGNICKYOUNG

Assigned

No issues.

Reported

No issues.

Comments

It's not even necessary to assign a number. The StructureTemplatePool class already has a rawTemplates field, containing a list of Pairs matching each of the pool's elements with their respective weights.

To state my aforementioned algorithm more simply:

1. Compute the sum of weights (let's call this number s) for all elements in the pool, using the rawTemplates field.
2. Pick a random number between 1 and s inclusive. Let's call this number c, for the chosen weight.
3. Iterate the rawTemplates list. For each element, subtract its weight from c. If c is now less than or equal to zero, choose the current element as the candidate element to place. If c is still positive, continue iterating.
4. Attempt to place the chosen candidate element. If it successfully places, then we're done. If not, then remove its entry from the rawTemplates field, and start at step 1 again.

Hopefully this makes some sense. Let me know if I need to elaborate on something.

This actually incurs quite a performance cost when generating Jigsaw structures, even for only moderate weight sums. A smarter approach can fix the issue altogether.

The following algorithm can be used as an effective replacement for the current JigsawPlacement functionality:

StructurePoolElement processList(List<Pair<StructurePoolElement, Integer>> candidatePiecesList, ...) {
    // The sum of all weights in the pool
    int totalCount = candidatePieces.stream().mapToInt(Pair::getSecond).reduce(0, Integer::sum);

    // Temp var for the piece we are attempting to add
    Pair<StructurePoolElement, Integer> chosenPiecePair;

    // Iterate the list of possible pieces in this pool
    while (candidatePieces.size() > 0) {
        // Select a random weight value from 1 to the sum of all weights in the pool.
        // We will subtract subsequent pool elements' weights from this number.
        // Once chosenWeight <= 0, we will select the pool element we are currently checking as the element to process.
        int chosenWeight = rand.nextInt(totalCount) + 1;

        // Iterate the candidate pieces (this will be the rawTemplates field in a StructureTemplatePool)
        for (Pair<StructurePoolElemen, Integer> candidate : candidatePieces) {
            int currentElementWeight = candidate.getSecond();
            chosenWeight -= currentElementWeight;
            if (chosenWeight <= 0) {
                // We have found the element we will attempt to generate
                chosenPiecePair = candidate;
                break;
            }
        }

        // Here, you should attempt to add the StructurePoolElement contained in chosenPiecePair, as JigsawPlacement$Placer#tryPlacingChildren already does.
        // If we successfully place the piece, we can return it (or a bool, or whatever) from this method to indicate successful placement.
        
        ...

        // If we fail to place this piece, simply do the following.
        // This will prepare us to grab another random element from the pool and try again.
        totalCount -= chosenPiecePair.getSecond(); // Remove this piece's weight from the total weight sum
        candidatePieces.remove(chosenPiecePair); // Remove this piece from the list so we don't attempt to add it again
    }
}

 

This function eliminates the need to create and reshuffle a huge list, while still providing the same functionality. All we need is the pool's rawTemplates field, which is what should be passed in as the candidatePiecesList. Instead of constructing n instances of an element for weight n, we simply subtract n from an integer - no need to allocate and randomize a bunch of objects.

The function can be called directly from JigsawPlacement$Placer#tryPlacingChildren in the following manner to replicate vanilla behavior:

public void tryPlacingChildren(...) {
    // Process the pool pieces, randomly choosing different pieces from the pool to spawn
    if (depth != this.maxDepth) {
        JigsawPiece generatedPiece = this.processList(new ArrayList<>(poolOptional.get().rawTemplates), ...);
        if (generatedPiece != null) continue; // Stop here since we've already generated the piece. No need to look at fallback pieces.
    }

    // Attempt to place one of the fallback pieces in the event none of the pool pieces worked
    this.processList(new ArrayList<>(fallbackOptional.get().rawTemplates), ...);
}

Overall, this change has pretty drastic performance improvements even for pools with only moderate weight sums.

For a modified version of the JigsawPlacement$Placer#tryPlacingChildren function which includes these changes, see here.