I’m currently developing a 3d multiplayer game in Monogame and noticing freezing every 5 seconds and quite allot of garbage collection. I’m trying to solve the issue by finding what creates garbage for the garbage collector and remove the garbage.
The below examples are only a smart part of a very large project which is coded in the same way as the examples shown, for example the games networking initializes variables in runtime as it collects data from the server, for example this code is checking what a player is wearing. This code is sent by the server when a player changes any gear it is wearing to update other players with the changes. (this code is called only in a specific situation)
if (messageTitle == "Equipment")
{
string who = msg.ReadString();
//get the item names
string helmet = msg.ReadString();
string shoulder = msg.ReadString();
string chest = msg.ReadString();
string shirt = msg.ReadString();
string cape = msg.ReadString();
string bracers = msg.ReadString();
string gloves = msg.ReadString();
string belt = msg.ReadString();
string pants = msg.ReadString();
string boots = msg.ReadString();
string slot1 = msg.ReadString();
string slot2 = msg.ReadString();
string slot3 = msg.ReadString();
string slot4 = msg.ReadString();
if (unitDatabase.realUnits.ContainsKey(who) == true)
{
CheckEquipment(who + "_Equipment_Helmet", helmet);
CheckEquipment(who + "_Equipment_Shoulder", shoulder);
CheckEquipment(who + "_Equipment_Chest", chest);
CheckEquipment(who + "_Equipment_Shirt", shirt);
CheckEquipment(who + "_Equipment_Cape", cape);
CheckEquipment(who + "_Equipment_Bracers", bracers);
CheckEquipment(who + "_Equipment_Gloves", gloves);
CheckEquipment(who + "_Equipment_Belt", belt);
CheckEquipment(who + "_Equipment_Pants", pants);
CheckEquipment(who + "_Equipment_Boots", boots);
CheckEquipment(who + "_Equipment_slot1", slot1);
CheckEquipment(who + "_Equipment_slot2", slot2);
CheckEquipment(who + "_Equipment_slot3", slot3);
CheckEquipment(who + "_Equipment_slot4", slot4);
}
}
But the code below is always sent by the server unreliably so mostly every frame it will be initializing these variables.
if (messageTitle == "Stats")
{
string who = msg.ReadString();
int health = msg.ReadInt32();
int mana = msg.ReadInt32();
int energy = msg.ReadInt32();
int rage = msg.ReadInt32();
bool inCombat = msg.ReadBoolean();
int experience = msg.ReadInt32();
if (unitDatabase.realUnits.ContainsKey(who) == true)
{
unitDatabase.realUnits[who].attributes.health = health;
unitDatabase.realUnits[who].attributes.mana = mana;
unitDatabase.realUnits[who].attributes.energy = energy;
unitDatabase.realUnits[who].attributes.rage = rage;
unitDatabase.realUnits[who].attributes.inCombat = inCombat;
if (unitDatabase.realUnits[who].attributes.experience != experience)
{
int difference = experience - unitDatabase.realUnits[who].attributes.experience;
unitDatabase.realUnits[who].attributes.experience = experience;
floatingTextDatabase.AddFloatingText("XP: " + difference, unitDatabase.realUnits[who], 0.35f, new Vector3(0, 0, 20), Color.Blue);
}
}
}
Everything received from the server is setup to receive data in this way, I believe the initializing in runtime all these variables would be creating allot of memory for the garbage collector which may be causing the 1 second freezing every 5seconds.
public void Draw(SpriteBatch spriteBatch, SpriteFont font)
{
#region DrawAllScreenFloatingText
for (int i = 0; i < floatingText.Count(); i++)
{
string message = floatingText[i].text.ToString();
Vector2 origin = font.MeasureString(message) / 2;
float textSize = floatingText[i].size;
Color backdrop = new Color((byte)50, (byte)0, (byte)0, (byte)MathHelper.Clamp(floatingText[i].fade, 0, 255));
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(-4 * textSize, -4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(-4 * textSize, 4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(4 * textSize, -4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(4 * textSize, 4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position), new Color(floatingText[i].color.R, floatingText[i].color.G, floatingText[i].color.B, (byte)MathHelper.Clamp(floatingText[i].fade, 0, 255)), 0, origin, textSize, 0, 1);
}
#endregion
}
The above code is drawing floating text on the screen, like damage being done to an enemy. Allot of the drawing would be handled like this and would be drawn every frame if there is damage being done to something. As you can see I would be initializing a string, vector2, float and color variable each draw frame multiplied by each damage number shown.
public void Draw(GraphicsDevice graphics, BasicEffect basicEffect, Effect modelEffect, MeshRenderer meshRenderer, ItemDatabase itemDatabase, ThirdPersonCamera camera, Weather weather, LightDatabase lightDatabase, AudioList audioList)
{
sw = new Stopwatch();
sw.Start();
if (camera.target != null)
{
foreach (var unit in realUnits)
{
//only draw close enough to us
double distance = Math.Sqrt((unit.Value.body.position.X - camera.target.body.position.X) * (unit.Value.body.position.X - camera.target.body.position.X) +
(unit.Value.body.position.Y - camera.target.body.position.Y) * (unit.Value.body.position.Y - camera.target.body.position.Y) +
(unit.Value.body.position.Z - camera.target.body.position.Z) * (unit.Value.body.position.Z - camera.target.body.position.Z));
if (distance < unit.Value.renderDistance)
{
//only draw units inside our view
if (camera.InsideCamera(unit.Value.body.position) == true || camera.target == unit.Value)
{
unit.Value.Draw(graphics, unitList[unit.Value.name], arrow, basicEffect, meshRenderer, itemDatabase, camera, weather, lightDatabase);
}
}
}
}
sw.Stop();
if (drawMs < sw.Elapsed.TotalMilliseconds) drawMs = sw.Elapsed.TotalMilliseconds;
}
Another code being updated every draw frame, it checks if the player being drawn is within view distance of the camera and then within the view frustum of the camera. You can see it is initializing a double every draw frame.
and inside the unit.Value.Draw(); function its initializing:
new SamplerState //for the shadow casting
Matrix[] bones
Matrix getWorld //the players world transform into shader
Matrix worldMatrix
float colorR //ambient sky color
float colorG //ambient sky color
float colorB //ambient sky color
int MAXLIGHTS //total light sources in scene
Vector3[] PointLightPosition = new Vector3[MAXLIGHTS];
Vector4[] PointLightColor = new Vector4[MAXLIGHTS];
float[] PointLightPower = new float[MAXLIGHTS];
float[] PointLightRadius = new float[MAXLIGHTS];
These would be initialized inside the draw call every draw frame if this player is within view distance of the player and view frustum of the camera.
I believe all these initialized variables in runtime every frame would be creating allot of garbage collection. Before I rework the whole game to eliminate calling new variables each frame I wanted to make sure this could be the reason for the garbage collection filling up and freezing the game every 5seconds.
Thank you for taking the time to read my question.
Edit: adding images of VS2019 profiler
Just wanted to update everyone the freezing was actually not due to garbage collection, It was actually because of this line of code.
if (pingDelay > 3)
{
ping = Int32.Parse(pingSender.Send(client.ServerConnection.RemoteEndPoint.Address, 120, Encoding.ASCII.GetBytes(“0”), options).RoundtripTime.ToString());
pingDelay = 0;
}
pingSender.Send() is supposed to be Async. Because it wasn’t it was freezing the game while it waited for the server to return the result.