Dev Blog
Hi, this is the Stranded III development blog (see also Forum Thread, Comment Thread).
Entry 46 - Newsletter, More Net Code Stuff & Clay - June 12, 2016
Newsletter
Some people asked for a newsletter so I prepared one.
Subscribe here
I will only send a newsletter when there's actually a playable version or other REALLY important news.
I won't send a weekly newsletter for the dev blog because I think it would be too annoying.
Network Messages²
I want to give you some more detailed insights into the message code generation I talked about last week.
Skip this part if you don't care about technical stuff
The messages are defined as simple text files. The first line commonly contains a number which defines the message ID which is used to identify this message.
This is followed by fields and optionally sub messages which form the content of the network message. The indention level is used for sub messages and conditions. No brackets.
Here's a very basic example for a simple message:
It uses message ID 245 and it only contains one integer, called "Timestamp". The code generation will create this class from it (usings and namespaces removed):
The name of the class is the file name of the definition file by the way.
Of course the system is also capable of doing more complex stuff! Sub messages for example. And "repetitions" - I stole that idea from Google's Protobufs. They are basically arrays of data.
So this is a network message definition to represent a school class. Each class has a name, a teacher and multiple pupils.
The pupils and the teacher are persons so they can be serialized using the same sub message (Person). Each person has a first name, a last name and an age in this example.
The <= 30 in the repeat statement defines that we expect a maximum of 30 pupils in a class. This is an important information for serialization because this way we know that we can use a single byte to encode the number of pupils. Otherwise we might have to use 2 or more bytes.
We could also just put 30 without <=. In this case the system would always send and receive 30 pupils. Advantage: No additional value for the amount has to be sent. Disadvantage: We always send 30 pupils. So this would only make sense in case all classes have exactly 30 pupils. Otherwise we might waste some bandwidth and memory.
This is the generated code from this definition:
The above sample contains just a single sub message. In theory the amount of sub messages is unlimited and they can also be nested infinitely.
Another cool feature I integrated is bit packing. Bytes are the smallest data unit you can send over the internet. A Boolean (or bit) however is an even smaller unit. A byte can hold up to 8 Boolean values. Therefore it would be a waste of bandwidth to send each Boolean value as a single byte. The system is aware of this and packs up to 8 Boolean values together to reduce the message size.
Here's an example:
Resulting code:
Only 2 bytes to encode all these Boolean values! There's even room for 5 more in these 2 bytes! Eureka!
There are also conditions as I mentioned last week but I won't show an example for that. It basically just generates an "if" around some of the statements in the read and write code.
Also note that this stuff is still in a really early development stage and a lot of things are still missing.
A lot of error handling code and sanity checks for example. Or constructors. Or ToString overrides...
Clay
Here comes another highly complicated model. Rocket science level: Over 9000! May I present to you, a clay... blob.
click to enlarge
Hey, maybe I could reuse it as potatoe model!?
Clay Pot
Here's something you'll be able to craft later using that clay!
Turns out that modelling a clay pot in blender is not very hard when you use the right base shape to start from. The torus seems to be a pretty good choice for that. I started with the top ring and simply extruded it downwards. The bottom was then closed with faces.
Afterwards I merged some faces inside to reduce the poly count a bit and moved the bottom inside a bit upwards. This should prevent other geometry underneath the clay pot from glitching through the bottom too easily. Voilà!
watch the clay pot modelling video on YouTube
Lifebuoy
I also worked on a lifebuoy model but it's not textured yet. Try to enjoy the grayness.
click to enlarge
Some people asked for a newsletter so I prepared one.
Subscribe here
I will only send a newsletter when there's actually a playable version or other REALLY important news.
I won't send a weekly newsletter for the dev blog because I think it would be too annoying.
Network Messages²
I want to give you some more detailed insights into the message code generation I talked about last week.
Skip this part if you don't care about technical stuff
The messages are defined as simple text files. The first line commonly contains a number which defines the message ID which is used to identify this message.
This is followed by fields and optionally sub messages which form the content of the network message. The indention level is used for sub messages and conditions. No brackets.
Here's a very basic example for a simple message:
1
2
3
2
3
245
int:Timestamp
int:Timestamp
It uses message ID 245 and it only contains one integer, called "Timestamp". The code generation will create this class from it (usings and namespaces removed):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PingMsg : INetworkMessage
{
public const int MessageID = 245;
public int Timestamp;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
p.WriteInt(Timestamp);
}
public void Read(Packet p)
{
Timestamp = p.ReadInt();
}
}
{
public const int MessageID = 245;
public int Timestamp;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
p.WriteInt(Timestamp);
}
public void Read(Packet p)
{
Timestamp = p.ReadInt();
}
}
The name of the class is the file name of the definition file by the way.
Of course the system is also capable of doing more complex stuff! Sub messages for example. And "repetitions" - I stole that idea from Google's Protobufs. They are basically arrays of data.
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
123
string:ClassName
Person:Teacher
repeat:Person:Pupils:<=30
message:Person
string:FirstName
string:LastName
byte:Age
string:ClassName
Person:Teacher
repeat:Person:Pupils:<=30
message:Person
string:FirstName
string:LastName
byte:Age
So this is a network message definition to represent a school class. Each class has a name, a teacher and multiple pupils.
The pupils and the teacher are persons so they can be serialized using the same sub message (Person). Each person has a first name, a last name and an age in this example.
The <= 30 in the repeat statement defines that we expect a maximum of 30 pupils in a class. This is an important information for serialization because this way we know that we can use a single byte to encode the number of pupils. Otherwise we might have to use 2 or more bytes.
We could also just put 30 without <=. In this case the system would always send and receive 30 pupils. Advantage: No additional value for the amount has to be sent. Disadvantage: We always send 30 pupils. So this would only make sense in case all classes have exactly 30 pupils. Otherwise we might waste some bandwidth and memory.
This is the generated code from this definition:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ShoolClass : INetworkMessage
{
public const int MessageID = 123;
public string ClassName;
public Person Teacher;
public Person[] Pupils;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
p.WriteString(ClassName);
Teacher.Write(p);
int c = Pupils.Length;
p.WriteByte((byte)c);
for (int i = 0; i < c; i++) {
Pupils[i].Write(p);
}
}
public void Read(Packet p)
{
ClassName = p.ReadString();
Teacher = new Person(); Teacher.Read(p);
int c = p.ReadByte();
Pupils = new Person[c];
for (int i = 0; i < c; i++) {
Pupils[i] = new Person(); Pupils[i].Read(p);
}
}
public class Person : INetworkMessage
{
public string FirstName;
public string LastName;
public int Age;
public int ID { get { return 0; } }
public void Write(Packet p)
{
p.WriteString(FirstName);
p.WriteString(LastName);
p.WriteInt(Age);
}
public void Read(Packet p)
{
FirstName = p.ReadString();
LastName = p.ReadString();
Age = p.ReadInt();
}
}
}
{
public const int MessageID = 123;
public string ClassName;
public Person Teacher;
public Person[] Pupils;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
p.WriteString(ClassName);
Teacher.Write(p);
int c = Pupils.Length;
p.WriteByte((byte)c);
for (int i = 0; i < c; i++) {
Pupils[i].Write(p);
}
}
public void Read(Packet p)
{
ClassName = p.ReadString();
Teacher = new Person(); Teacher.Read(p);
int c = p.ReadByte();
Pupils = new Person[c];
for (int i = 0; i < c; i++) {
Pupils[i] = new Person(); Pupils[i].Read(p);
}
}
public class Person : INetworkMessage
{
public string FirstName;
public string LastName;
public int Age;
public int ID { get { return 0; } }
public void Write(Packet p)
{
p.WriteString(FirstName);
p.WriteString(LastName);
p.WriteInt(Age);
}
public void Read(Packet p)
{
FirstName = p.ReadString();
LastName = p.ReadString();
Age = p.ReadInt();
}
}
}
The above sample contains just a single sub message. In theory the amount of sub messages is unlimited and they can also be nested infinitely.
Another cool feature I integrated is bit packing. Bytes are the smallest data unit you can send over the internet. A Boolean (or bit) however is an even smaller unit. A byte can hold up to 8 Boolean values. Therefore it would be a waste of bandwidth to send each Boolean value as a single byte. The system is aware of this and packs up to 8 Boolean values together to reduce the message size.
Here's an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
5
bool:This
bool:Is
bool:A
bool:Bit
bool:Packing
bool:Example
bool:ExclamationMark
bool:Thats
bool:Pretty
bool:Awesome
bool:ThreeExclamationMarks
bool:This
bool:Is
bool:A
bool:Bit
bool:Packing
bool:Example
bool:ExclamationMark
bool:Thats
bool:Pretty
bool:Awesome
bool:ThreeExclamationMarks
Resulting code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class BitPacking : INetworkMessage
{
public const int MessageID = 5;
public bool This;
public bool Is;
public bool A;
public bool Bit;
public bool Packing;
public bool Example;
public bool ExclamationMark;
public bool Thats;
public bool Pretty;
public bool Awesome;
public bool ThreeExclamationMarks;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
byte bitFlags = 0;
if (This) { bitFlags += 1; }
if (Is) { bitFlags += 2; }
if (A) { bitFlags += 4; }
if (Bit) { bitFlags += 8; }
if (Packing) { bitFlags += 16; }
if (Example) { bitFlags += 32; }
if (ExclamationMark) { bitFlags += 64; }
if (Thats) { bitFlags += 128; }
p.WriteByte(bitFlags);
bitFlags = 0;
if (Pretty) { bitFlags += 1; }
if (Awesome) { bitFlags += 2; }
if (ThreeExclamationMarks) { bitFlags += 4; }
p.WriteByte(bitFlags);
}
public void Read(Packet p)
{
byte bitFlags = p.ReadByte();
This = (bitFlags & 1) != 0;
Is = (bitFlags & 2) != 0;
A = (bitFlags & 4) != 0;
Bit = (bitFlags & 8) != 0;
Packing = (bitFlags & 16) != 0;
Example = (bitFlags & 32) != 0;
ExclamationMark = (bitFlags & 64) != 0;
Thats = (bitFlags & 128) != 0;
bitFlags = p.ReadByte();
Pretty = (bitFlags & 1) != 0;
Awesome = (bitFlags & 2) != 0;
ThreeExclamationMarks = (bitFlags & 4) != 0;
}
}
{
public const int MessageID = 5;
public bool This;
public bool Is;
public bool A;
public bool Bit;
public bool Packing;
public bool Example;
public bool ExclamationMark;
public bool Thats;
public bool Pretty;
public bool Awesome;
public bool ThreeExclamationMarks;
public int ID { get { return MessageID; } }
public void Write(Packet p)
{
byte bitFlags = 0;
if (This) { bitFlags += 1; }
if (Is) { bitFlags += 2; }
if (A) { bitFlags += 4; }
if (Bit) { bitFlags += 8; }
if (Packing) { bitFlags += 16; }
if (Example) { bitFlags += 32; }
if (ExclamationMark) { bitFlags += 64; }
if (Thats) { bitFlags += 128; }
p.WriteByte(bitFlags);
bitFlags = 0;
if (Pretty) { bitFlags += 1; }
if (Awesome) { bitFlags += 2; }
if (ThreeExclamationMarks) { bitFlags += 4; }
p.WriteByte(bitFlags);
}
public void Read(Packet p)
{
byte bitFlags = p.ReadByte();
This = (bitFlags & 1) != 0;
Is = (bitFlags & 2) != 0;
A = (bitFlags & 4) != 0;
Bit = (bitFlags & 8) != 0;
Packing = (bitFlags & 16) != 0;
Example = (bitFlags & 32) != 0;
ExclamationMark = (bitFlags & 64) != 0;
Thats = (bitFlags & 128) != 0;
bitFlags = p.ReadByte();
Pretty = (bitFlags & 1) != 0;
Awesome = (bitFlags & 2) != 0;
ThreeExclamationMarks = (bitFlags & 4) != 0;
}
}
Only 2 bytes to encode all these Boolean values! There's even room for 5 more in these 2 bytes! Eureka!
There are also conditions as I mentioned last week but I won't show an example for that. It basically just generates an "if" around some of the statements in the read and write code.
Also note that this stuff is still in a really early development stage and a lot of things are still missing.
A lot of error handling code and sanity checks for example. Or constructors. Or ToString overrides...
Clay
Here comes another highly complicated model. Rocket science level: Over 9000! May I present to you, a clay... blob.
click to enlarge
Hey, maybe I could reuse it as potatoe model!?
Clay Pot
Here's something you'll be able to craft later using that clay!
Turns out that modelling a clay pot in blender is not very hard when you use the right base shape to start from. The torus seems to be a pretty good choice for that. I started with the top ring and simply extruded it downwards. The bottom was then closed with faces.
Afterwards I merged some faces inside to reduce the poly count a bit and moved the bottom inside a bit upwards. This should prevent other geometry underneath the clay pot from glitching through the bottom too easily. Voilà!
watch the clay pot modelling video on YouTube
Lifebuoy
I also worked on a lifebuoy model but it's not textured yet. Try to enjoy the grayness.
click to enlarge