时间:2019-03-17 标签:c#3dgamegarbagecollectionfreeze
我目前正在 Monogame 中开发一款 3D 多人游戏,并注意到每 5 秒就会冻结一次,并且会进行大量垃圾收集。我试图通过找到垃圾收集器产生垃圾的原因并删除垃圾来解决这个问题。
下面的示例只是一个非常大的项目的一个智能部分,其编码方式与所示示例相同,例如游戏网络在从服务器收集数据时在运行时初始化变量,例如此代码正在检查什么是玩家正在佩戴。当玩家更改所佩戴的任何装备时,服务器会发送此代码,以将更改更新到其他玩家。 (此代码仅在特定情况下调用)
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);
}
}
但是下面的代码始终由服务器不可靠地发送,因此大多数情况下每一帧都会初始化这些变量。
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);
}
}
}
从服务器接收到的所有内容都设置为以这种方式接收数据,我相信在运行时初始化所有这些变量将为垃圾收集器创建内存分配,这可能会导致每 5 秒冻结 1 秒。
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
}
上面的代码在屏幕上绘制浮动文本,就像对敌人造成的伤害一样。绘图的分配将像这样处理,并且如果对某物造成损坏,则每帧都会绘制绘图。正如你所看到的,我将初始化一个字符串、向量2、浮点和颜色变量,每个并条机乘以显示的每个损坏数。
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;
}
每个绘制帧都会更新另一个代码,它检查正在绘制的玩家是否在相机的视距内,然后在相机的视锥体内。您可以看到它正在初始化每个并条机的两倍。
并在unit.Value.Draw()内部;函数其初始化:
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];
如果该玩家位于玩家和相机视锥体的视野范围内,则这些将在每个绘制帧的绘制调用内初始化。
我相信在运行时每个帧中所有这些初始化的变量都会创建垃圾收集的分配。在我重新设计整个游戏以消除每帧调用新变量之前,我想确保这可能是垃圾收集每 5 秒填满并冻结游戏的原因。
感谢您花时间阅读我的问题。
编辑:添加 VS2019 分析器的图像
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
因此,我认为我将进行一个快速的项目测试,以测试垃圾收集器的运作方式。下面我添加了一个项目,该项目更新以下代码每个绘制调用(正是我目前正在做的)
这就是VS2019 Profiler记录的
下一个示例我在开头初始化string [] test []该程序只有
vs2019 profiler记录的内容
因此,从这个示例中,我似乎永远不想在运行时初始化变量,并尝试在程序开始时初始化所有变量。我需要重新使用相同的变量,以避免垃圾收集器冻结游戏。谢谢你回答的每个人。
So I figured I'll make a quick project testing how the garbage collector is working. Below I added a project that updates the below code every draw call (exactly what I'm currently doing)
This is what the VS2019 profiler recorded
The next example I initialize string[] test at the start of the program only
This is what the VS2019 profiler recorded
So from this example it looks like I never want to initialize variables during runtime and try to initialize all variables at the start of the program. I will need to re-use the same variables to avoid the garbage collector freezing the game. Thankyou everyone who replied.
只是想告诉大家,冻结实际上不是由于垃圾收集,实际上是因为这行代码。
pingSender.Send() 应该是异步的。因为它不是在等待服务器返回结果时冻结游戏。
Just wanted to update everyone the freezing was actually not due to garbage collection, It was actually because of this line of code.
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.