Jump to content

[1.8][Hate overload][SOLVED] TileEntity wtf :P


Ernio

Recommended Posts

Alert: Mucho reading.

 

I wouldn't come here if I hadn't debugged it for more than hour. I MUST be missing something.

 

Situation:

There is TileEntity that holds EnumMap<KeyX, Double> and container that holds SAME map. When TEs map change, container checks it and sends update to watchers (crafters).

This map is sent to client's TE ONLY when someone is looking at container (using #sendProgressBarUpdate).

 

Inside container's #detectAndSendChanges()

private double lastMapAmount = 0.0D; // in container

double currentMapAmout = this.tileEntity.getTotalAmount();
if (this.lastMapAmount != currentMapAmout)
{
System.out.println("LAST: " + this.lastMapAmount);
System.out.println("CUR: " + currentMapAmout);
for (KeyX key : KeyX.values())
{
	System.out.println(key);
	Double currentValue = this.tileEntity.getMap().get(key);
	System.out.println("CURRENT SERVER: " + currentValue);
	Double lastValue = this.map.get(key);
	System.out.println("LAST SERVER: " + lastValue);
	if (currentValue != lastValue)
	{
		System.out.println("NOT EXACT");
		if (currentValue == null || currentValue == 0.0D)
		{
			System.out.println("NULL OR ZERO");
			this.map.remove(key);
		}
		else
		{
			System.out.println("CHANGED");
			this.map.put(key, currentValue);
		}
		sendToCrafters(material.ordinal()); // sends changes
	}
}
this.lastMapAmount = currentMapAmout;
}

private void sendToCrafters(int id)
{
for (int i = 0; i < this.crafters.size(); ++i)
{
	ICrafting iCrafting = (ICrafting) this.crafters.get(i);
	iCrafting.sendProgressBarUpdate(this, id, this.tileEntity.getField(id)); //actually sends changes
}
}

Receiver (container):

@SideOnly(Side.CLIENT)
public void updateProgressBar(int id, int data)
{
this.tileEntity.setField(id, data);
}

 

And in tileEntity:

@Override
public void setField(int id, int value)
{
System.out.println("SETTING KEY");
if (value <= 0)
{
	System.out.println("REMOVEING CLIENT KEY");
	this.map.remove(KeyX.values()[id]);
}
else
{
	System.out.println("NEW KEY VALUE: " + KeyX.values()[id] + " ; " + Double.valueOf(value));
	this.map.put(KeyX.values()[id], new Double(value));
	System.out.println(this.map.get(KeyX.values()[id]));
}
}

 

At start of update(), (in tileEntity) there is:

@Override
public void update()
{
System.out.println("Something: TIN:  " + this.map.get(KeyX.TIN));

 

And console print: (// my actions)

// I am looking at container
//Here here we can see prints from update()
// TIN is my  only key in map.
[12:19:46] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
//This is the #detectAndSendChanges() part, last is not equal to current (something changed) thus, sending new values to me, looking at container.
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:101]: LAST: 0.0
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:102]: CUR: 100.0
// first and only Enum
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:105]: TIN
// the new value read from TileEntity
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:107]: CURRENT SERVER: 100.0
// old value that was saved in container
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:109]: LAST SERVER: null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:112]: NOT EXACT
[12:19:47] [server thread/INFO] [sTDOUT]: [something.ContainerSomething:detectAndSendChanges:120]: CHANGED
// packet is being sent (update progress bar)
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:464]: SETTING KEY
// as you can see - it's received
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:472]: NEW KEY VALUE: TIN ; 100.0
// and when I get it from client's TileEntity - IT'S CORRECT!
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:474]: 100.0
//Server's value is updated.
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
// AND BAM - client's not - this is literally same/next tick Client received update few lines above.
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null

 

My questio nis WTF?

I made sure that map is NOT accessed ANYWHERE else!

 

What don't I know?

 

Thanks.

 

EDIT:

I know that double is lost after packet, i am trimming double to int for client, they don't need it, only server has full value.

 

EDIT 2: And yes, those are same objects, checked with System.identityHashCode(this.map)

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

This is in TE:

public double getTotalAmount()
{
double total = 0.0;
for (Double amount : this.map.values())
{
	total += amount; // note: value will never be null
}
return total;
}

 

And yes, map is in both container and TE. TE changes map, container will detect if last size is != current size, and then will scan it for changes that will be sent (double will be trimmed to int).

 

I can go even without those current/last size (that was a simple cut of operations to about 25% when there are no changes), still that doesn't change anything.

 

Problem lies exactly here:

[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:464]: SETTING KEY
// as you can see - it's received
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:472]: NEW KEY VALUE: TIN ; 100.0
// and when I get it from client's TileEntity - IT'S CORRECT!
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:setField:474]: 100.0 // size of map is 1
//Server's value is updated.
[12:19:47] [server thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  100.0
// AND BAM - client's not - this is literally same/next tick Client received update few lines above.
[12:19:47] [Client thread/INFO] [sTDOUT]: [something.TileEntitySomething:update:78]: Something: TIN:  null // size of same map is 0

 

The key is being set, the EnumMap object is not changed (same one). When I get the value of key after setting it's 100.0, but just tiny bit after (first tick possible) it's again null - in same damn map.

How is that possilbe?

 

EDIT

I realized why you wrote "keep a clone (!)" - and indeed, those two maps are "same" in manners they are initialized in same way, they are not exactly pointing to one object. So no issue here.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

width=380 height=214http://img-9gag-lol.9cache.com/photo/aPy7mwn_460sa_v1.gif[/img]

 

I strongly belive that this has something to do with threads, because at my level of understanding java internals - it's impossible for single object to give different values for two different callers.

 

I don't think this is how it is supposed to work.

 

Just after setting it to 100.0, the #update():

@Override
public void update()
{
System.out.println("Something: TIN:  " + this.map.get(KeyX.TIN));

 

Will never return just-changed values. It's like the map is not synchronized with the object that was changed.

Funny enough that I actually did outside check and this exact object accessed on CLIENT from outside update() actually HAS new-changed value. (it's like #update() doesn't care about changes and prints out old stuff (not updated map object), even if I am directly referencing it).

 

I am done...

Once again, my arch nemesis - Vanilla - You did your best to make me waste another hours of life. (read signature, everything has reasons)

 

One last thing - how is this possible for same-damn object (belive me when I say "same") to return different value for different callers? I've neve been to synchronized Java, and if I am guessing right - Vanilla does't do well with it.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Irrelevant code has been removed.

 

Container:

http://pastebin.com/MhsgwVP7

 

TileEntity:

http://pastebin.com/9NEkGvR9

 

Those are abstracts, if you'd have to actually understand what's going on in implementation - took me more than week to just design it, waste of time looking there. I am SURE that there is no error in removed code (checked word by word, call by call).

 

TheKey is a simple Enum containing "TIN".

 

EDIT Note: If the value reaches 0.0D or null the Entry is deleted. Double "value" can never be null during map iteration.

 

@Diesieben, will this suffice or you wanted whole mod implementation to test it? The mistake is most certainly not in anything I wrote. There is no "REAL" error here - the values exist, it's just (as described before) that they are (changes) ignored by onUpdate().

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Thats wasn't it, tho it might cause miscalculations in future, thx.

 

As to problem - it indeed turned out to be vanillas behaviour.

 

Something I asked about few days ago - "shouldRefresh" method.

It is defaulted to true for non-vanilla TEs.

 

This caused my client-side container to have "old" TE reference in it, while the world's TE in BlockPos I was currently operating on got new TE when its blockState changed from normal to active block.

 

While I was tracking and printing out everything inside, the error was actually totally outside and I realized it when started printing object's hex int of "this" (the TileEntity itself).

 

Remember kids:

When blockState changes - while the server's TE is kept intact - the client one will, by default, change to new TE - thus creating outdated object references.

 

Fix:

Made #shouldRefresh(...) return false there is relation between blocks' blockStates "normal" and "active".

 

Note for others:

NEVER return "false" in all cases - it will cause client's world to not remove TE in given place, even when server removes it.

While it is NOT unsafe (something like ghost block) and will not cause errors (TE will get removed on world-reload), it might cause unwanted client actions (e.g particle spawning).

 

SOLVED.

In your face vanilla! #hate

 

 

EDIT

Btw, there is typo in forge :D

public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newSate) //newSate

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Note for others:

NEVER return "false" in all cases - it will cause client's world to not remove TE in given place, even when server removes it.

While it is NOT unsafe (something like ghost block) and will not cause errors (TE will get removed on world-reload), it might cause unwanted client actions (e.g particle spawning).

That's what the notes say, but all vanilla TileEntities return false by default, so why are modded TEs not automatically removed client-side when removed server-side, and what are we supposed to do about it in those cases where we must return false?

 

In fact, I just return false by default because 1. that's what vanilla does, and 2. 99% of the time that's the behavior I expect my TE to perform, i.e. not destroy itself and lose all data whenever the block changes state.

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.