Jump to content

[1.7.10] Tile Entity Owned Resources and shouldRefresh


Bystander

Recommended Posts

This has probably been solved a million times so hopefully somebody can point me the right way.

 

One of my Tile Entities has a looping sound - as in a proper looping ISound, not one I restart after some ticks. I'm using a custom PositionedSound class to do this and setting repeat from within it. I have logic to start and stop the looping audio using the SndManager which all works fine.

 

I have to create an instance of the PositionedSound on the TileEntity and hang onto it in order to be able to stop it.

 

The problem comes when my tile entity gets a block update. This triggers a call to shouldRefresh() which by default returns true for all non vanilla tile entities. What happens then is that the tile entity is refreshed - meaning a new one is made and the old one is orphaned - meaning the reference to the owned resource goes with it.

 

I guess most people probably don't notice this happening if all their state is in NBT, however this tile entity needs to hang on to the owned PositionedSound resource. Without it there is no way to stop the sound looping.

 

I could force shouldRefresh to return false like a vanilla TE, but it's decorated with plenty of warnings about not doing this. Plus I'm guessing this isn't the only way TEs are refreshed so it may not even be a solution.

 

Is the only solution here to make my own registry or is there an accepted way of passing owned resources to your predecessor on refresh?

 

 

Link to comment
Share on other sites

If it is needed, why don't you return false when only blockstate changes?

(you would have to return false for id change)

& It seemed that it is always called on every id/blockstate change.

I. Stellarium for Minecraft: Configurable Universe for Minecraft! (WIP)

II. Stellar Sky, Better Star Rendering&Sky Utility mod, had separated from Stellarium.

Link to comment
Share on other sites

I don't think there is a robust way of controlling the resource lifetime, this is more a Java problem than a minecraft problem.  You'll probably have a similar issue when the chunk unloads and the TileEntity is orphaned.  When a TileEntity is (re)created, it's just an empty shell until something reloads its contents by calling readFromNBT.

 

You could perhaps make your ticked looping sound responsible for checking if the TileEntity exists, rather than the other way around.

 

Alternatively, as you said, you could have a sound registry that you service at regular intervals using a tick handler, along with the tile entity coordinates for each looping sound.  (This is what I would try first personally).

 

-TGG

 

Link to comment
Share on other sites

One more bit of info for anyone else who ends up in this thread as it was confusing me why this behaviour didn't cause all kinds of other problems in mods.

 

Specifically when this refresh happens the orphaned TE appears to get no warning, no chance to flush or store state and there's no copy constructor call on the new TE to transfer state. It seemed to me like any mod that stores inventory on a TE for instance would be screwed.

 

The reality is the block is only orphaned/removed on the client side, and then only if something changes the block meta data, writing to it or even marking the block for update isn't enough to trigger the tile entity refresh.

 

I can still imagine it causing all kinds of client side hazards for any TE using metadata to transfer data to the client - I might try writing an evil block that forces fake block meta data updates on neighbouring blocks. ;)

Link to comment
Share on other sites

> The reality is the block is only orphaned/removed on the client side,

I don't think this is true.  The server unloads the chunk with TileEntities when the player is a certain distance away, so you would get the same problem.

 

The inventory is saved in NBT format on the server side, which is why TileEntities don't lose information when they're unloaded.  Which you can't do for your object reference unless you use some sort of key, which comes back to using a registry.

 

-TGG

 

PS see here for a bit of background info - which you may know already, but just to be sure

http://greyminecraftcoder.blogspot.com.au/2015/01/tileentity.html

 

Link to comment
Share on other sites

Oh, by the way for anyone else doing this (looping audio), I solved the resource retention issue the simpler way in the end.

 

A keyed registry seemed like overkill and there's already a Minecraft ITickableSound interface the sound manager checks for.

 

You just need to implement isDonePlaying on the PositionedSound object and in that check back with the parent TileEntity.

 

The only thing that annoys me about the solution is the call to GetTileEntity() each sound manager tick - seems like overkill.

 

A quick guide to looping audio in 1.7.10 - in so far as it works for me.

 

Define this and Implement it on your Tile Entity.

public interface IShouldLoop {
boolean continueLoopingAudio();
}

 

Create this class.

public class LoopingSound extends PositionedSound implements ITickableSound {

private int x,y,z;
private boolean shouldBePlaying = true;

public LoopingSound(ResourceLocation resource,TileEntity parent) {
	super(resource);
	repeat = true;
	xPosF = parent.xCoord+0.5f;
	yPosF = parent.yCoord+0.5f;
	zPosF = parent.zCoord+0.5f;
	x = parent.xCoord;
	y = parent.yCoord;
	z = parent.zCoord;
}

@Override
public void update() {
}

@Override
public boolean isDonePlaying() {
	if (shouldBePlaying)
	{
		// we should only be playing if parent still exists and says we are playing - assume not
		shouldBePlaying = false; 
		// should never be here on the server, but just in case
		World world = Minecraft.getMinecraft().theWorld;
		if (world.isRemote)
		{
			TileEntity parent = world.getTileEntity(x, y, z);
			if (parent != null)
			{
				if (parent instanceof IShouldLoop)
				{
					IShouldLoop iShouldLoop = (IShouldLoop)parent;
					shouldBePlaying = iShouldLoop.continueLoopingAudio();
				}
			}
		}
	}
	return !shouldBePlaying;
}
}

 

You kick the loop off like this (has to be client side obviously).

ResourceLocation audioLoop = new ResourceLocation("mymod","loopname");

LoopingSound myLoop = new LoopingSound(audioLoop,this);
Minecraft.getMinecraft().getSoundHandler().playSound(myLoop);

 

The sound files are specified the same way you'd do normal one shot audio, i.e. they go in assets/mymod/sounds/ (use .ogg format)

Create a sounds.json in assets/mymod - use the format of the minecraft one as a template or use something like this.

 

{
"loopname": {
	"category": "master",
	"sounds": [
		"loopname"
	]
},
"bigswitch": {
	"category": "master",
	"sounds": [
		"bigswitch"
	]
}
}

 

 

 

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.