[Verse-dev] [Verse-dev]understanding event collapse

Eskil Steenberg verse-dev@blender.org
Wed, 18 Aug 2004 02:28:17 +0200


Hi

This has turned out to be an issue that is hard to shake, and I have 
perhaps been too quick to dismiss proposed changes with out properly 
explaining how this works. (i think i have written something like this 
in the past but anyway....)

The basis of this system is that UDP network packets can arrive out of 
order. or get lost (if a packet gets lost it will be resent and hence 
out of order, so in this mail i will only refer to this as out of order.)

if we now consider us sending thees 2 commands:

verse_send_g_layer_create
verse_send_b_init_dimensions

if thees two commands arrive out of order it doesnt matter because the 
deal with two different parts of the data set that can be updated 
independently.

However, what if the two commands are the same commands? lets imagine 
that we send the following two commands:

verse_send_b_init_dimensions(1, 128, 128, 1);
verse_send_b_init_dimensions(2, 256, 256, 1);

if thees two commands arrive out of order it doesnt matter either 
because even if they use the same command they deal with two different 
nodes. The problem comes when you have two commands that deals with the 
same data:

verse_send_b_init_dimensions(2, 128, 128, 1);
verse_send_b_init_dimensions(2, 256, 256, 1);

Now this would be parsed as the bitmap should have a size of 128*128*1 
and then it is changed to 256*256*1. If the two arrive out of order 
there will be a problem, because the node will first be 256*256*1 and 
then 128*128*1. In other word we would get the wrong result and the two 
sides would be out of sync.

As far as i know there is only 2 ways of solving this, the first and 
obvious one is to wait for both commands and the re-order them before 
parsing them. This is what TCP does and it works fine for file transfer. 
The problem with this is that you have to wait for the later packet 
before you can parse any of the data that should have came after it, so 
it creates a lock up. (the most common reason for out of order packets 
are packet re-send so the round trip to request a re-send and then get 
it can be quite long) 

In verse this problem had to be solved in a different way for 
performance reasons. Here is how we do it:

If we take a closer look at the commands used in the earlier examples we 
see that we can divide each command in to two parts, address and data:

Address: verse_send_b_init_dimensions(VNodeID node_id,
data: uint16 width, uint16 height, uint16 depth);

Any two commands with the same address will over write each others data. 
therefore we define the address and data block of all commands. (you can 
actually see the definitions of the commands in the v_cmd_def_ files in 
verse, wherever the enum VCGP_END_ADDRESS is used)

We can now quickly take any two commands and compare their address to 
see if they over write each other. So how do we use this to fix the out 
of order problem? well if we send:

verse_send_b_init_dimensions(2, 128, 128, 1);
verse_send_b_init_dimensions(2, 256, 256, 1);

and they arrive in the wrong order and the sender is asked to re-send 
the first command. The sender can then simply look in to its history and 
see that the second command over writes the first one. so it ends up 
ignoring the resend request to avoid delivering the two out of order. So 
all the receiver will only get is:

verse_send_b_init_dimensions(2, 256, 256, 1);

clear?

It gets a little more complicated. Lets take a look at thees two commands:

verse_send_g_layer_create(2, 3, "heat", VN_G_LAYER_VERTEX_REAL, 0, 0);
verse_send_g_layer_destroy(2, 3);

If you think about it the two functions have the same address (they both 
deal with node 2 layer 3), but they are not the same API call. That is 
true, but they are the same command in the network! internally in the 
spec the verse_send_g_layer_destroy is called an "Alias" of 
verse_send_g_layer_create. so lets see what this means. lets say we send 
these three commands.

verse_send_g_layer_create(2, 3, "heat", VN_G_LAYER_VERTEX_REAL, 0, 0);
verse_send_g_layer_destroy(2, 3);
verse_send_g_layer_create(2, 3, "cold", VN_G_LAYER_POLYGON_FACE_REAL, 0, 0);

they would set one layer, then destroy it and then re set it. but lets 
say this came out of order:

verse_send_g_layer_create(2, 3, "heat", VN_G_LAYER_VERTEX_REAL, 0, 0);
verse_send_g_layer_create(2, 3, "cold", VN_G_LAYER_POLYGON_FACE_REAL, 0, 0);
verse_send_g_layer_destroy(2, 3);

then the network protocol would remove the last call to get the correct 
output:

verse_send_g_layer_create(2, 3, "heat", VN_G_LAYER_VERTEX_REAL, 0, 0);
verse_send_g_layer_create(2, 3, "cold", VN_G_LAYER_POLYGON_FACE_REAL, 0, 0);

This means that the last command received rules disregarding what came 
before. a  callback has to be ready to change the state disregarding of 
what was before. if the client is asked to create a layer where there 
already is a layer, it has to overwrite the current state. you would get 
the exact same state if you only sent:

verse_send_g_layer_create(2, 3, "cold", VN_G_LAYER_POLYGON_FACE_REAL, 0, 0);

In this text i have used commands that you may not use too often as 
examples, but this system is in place for 90% of the API (the exceptions 
are mostly connection commands)

Now it becomes obvious why verse thinks that:

verse_send_g_layer_create(2, -1, "heat", VN_G_LAYER_VERTEX_REAL, 0, 0);
verse_send_g_layer_create(2, -1, "cold", VN_G_LAYER_POLYGON_FACE_REAL, 
0, 0);
verse_send_g_layer_create(2, -1, "moist", 
VN_G_LAYER_POLYGON_CORNER_REAL, 0, 0);

is the same as:

verse_send_g_layer_create(2, -1, "moist", 
VN_G_LAYER_POLYGON_CORNER_REAL, 0, 0);

I have been contemplating how to fix the illegal id problem, to make it 
side step the event compression as emil suggested, but this doesnt 
change the basic idea of one address per data, and that any command over 
writes all previous state disregarding of what was before. If we break 
that we break everything.

There are some things that you have to think about when you code verse 
that is in its very nature. when verse was designed we did not just take 
in to account usability, there where many factors. such as  performance. 
Verse is and was designed to be a low level API, and that means that you 
sacrifice ease of use for performance and control. I dont mind this 
because i know the a higher level API can be implemented on top of  
verse that fixes that.

Every one who writes apps for verse at some point or an other wants to 
change the API (me included, for every app Ive written)  This is why I 
have implemented Enough and other tools so that i more easely can do 
what i want whit verse. You are free to use my higher level API too, but 
to force people to use it just wrong. Purple is an example of an app 
that doesnt fit very well with how Enough was written so Emil is free 
not to us it and that is a good thing.

E