Skip to content

Dev Q&A

KilaBash edited this page Mar 27, 2023 · 12 revisions

How to use SyncData annotations?

You don't need to worry about any synchronization and serialization code for fields.

Check funtions of each annotation here. LDLib Wiki

How to add an item inventory / fluid sotrage / energy container?

You can create them via NotifiableItemStackHandler, NotifiableFluidTank, NotifiableEnergyContainer. In general, you should always use them. They notify all listeners of internal changes to improve performance.

Parameters include io and capabilityIO:

  • io: Whether it is regarded as input or output during recipe processing?
  • caabilityIO: Whether layer can use hopper, pipe interacte storage?

NOTE: In general, they are created as final fields (That's what we need for a syncdata system), set their base arguments in the constructor (you can pass args for subclasses to modify).

If you do not need to add a storage that can be used for recipe processing and providing capability, you can just use ItemStackTransfer, FluidStorage, they are more lightweight.

How to use update / ITickable?

The client update is always present and you can override the clientTick(), which works as well as 1.12.

But for the sake of performance, our machines are no longer always in an Tickable state. We introduce ITickSubscription for managed tick logic. Understand the basic concept of subscribing to periodic updates when they are needed and unsubscribe them when they are not.

For example, Automatic output of the machine requires periodic output of internal items to adjacent inventory. But most of the time this logic doesn't need to be executed. If there is no item inside the machine, or the automatic output is not set to active, or there is no adjacent block that can accept the item. Lets look at how we implement it in QuantumChest.

@Getter @Persisted @DescSynced
protected boolean autoOutputItems;
@Persisted @DropSaved
protected final NotifiableItemStackHandler cache; // inner inventory
protected TickableSubscription autoOutputSubs; 
protected ISubscription exportItemSubs;

// update subscription, subscribe if tick logic subscription is required, unsubscribe otherwise.
protected void updateAutoOutputSubscription() {
    var outputFacing = getOutputFacingItems(); // get output facing
    if ((isAutoOutputItems() && !cache.isEmpty()) // inner item non empty
            && outputFacing != null // has output facing
            && ItemTransferHelper.getItemTransfer(getLevel(), getPos().relative(outputFacing), outputFacing.getOpposite()) != null) { // adjacent block has inventory.
        autoOutputSubs = subscribeServerTick(autoOutputSubs, this::checkAutoOutput); // subscribe tick logic
    } else if (autoOutputSubs != null) { // unsubscribe tick logic
        autoOutputSubs.unsubscribe();
        autoOutputSubs = null;
    }
}

// output to nearby block.
protected void checkAutoOutput() {
    if (getOffsetTimer() % 5 == 0) {
        if (isAutoOutputItems() && getOutputFacingItems() != null) {
            cache.exportToNearby(getOutputFacingItems());
        }
        updateAutoOutputSubscription(); // dont foget to check if it's still available
    }
}

@Override
public void onLoad() {
    super.onLoad();
    if (getLevel() instanceof ServerLevel serverLevel) {
        // you cant call ItemTransferHelper.getItemTransfer while chunk is loading, so lets defer it next tick.
        serverLevel.getServer().tell(new TickTask(0, this::updateAutoOutputSubscription));
    }
    // add a listener to listen the changes of inner inventory. (for ex, if inventory not empty anymore, we may need to unpdate logic)
    exportItemSubs = cache.addChangedListener(this::updateAutoOutputSubscription);
}

@Override
public void onUnload() {
    super.onUnload(); //autoOutputSubs will be released automatically when machine unload
    if (exportItemSubs != null) {  //we should mannually release it.
        exportItemSubs.unsubscribe();
        exportItemSubs = null;
    }
}

// For any change may affect the logic to invoke updateAutoOutputSubscription at a time
@Override
public void setAutoOutputItems(boolean allow) {
    this.autoOutputItems = allow;
    updateAutoOutputSubscription();
}

@Override
public void setOutputFacingItems(Direction outputFacing) {
    this.outputFacingItems = outputFacing;
    updateAutoOutputSubscription();
}

@Override
public void onNeighborChanged(Block block, BlockPos fromPos, boolean isMoving) {
    super.onNeighborChanged(block, fromPos, isMoving);
    updateAutoOutputSubscription();
}

I know the code is a kinda long, but it's for performance, and thanks to the SyncData system, we've eliminated a lot of synchronization code, so please sacrifice a little for performance.

Clone this wiki locally