c# 3d game garbage collection 冻结

c# 3d game garbage collection freezing

我目前正在用 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
}

上面的代码在屏幕上绘制浮动文本,就像对敌人造成伤害一样。绘图的分配将像这样处理,如果对某些东西造成损坏,将在每一帧绘制。如您所见,我将在每个绘制帧乘以显示的每个损坏数字后初始化一个字符串、vector2、float 和颜色变量。

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;
}

每个绘制帧更新的另一个代码,它检查被绘制的玩家是否在相机的视野范围内,然后在相机的视锥内。你可以看到它在每个绘制帧都初始化一个 double。

和 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 分析器的图像

所以我想我会做一个快速项目来测试垃圾收集器的工作方式。下面我添加了一个项目,每次绘制调用都会更新下面的代码(正是我目前正在做的)

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    string[] test = new string[100000];

    // TODO: Add your drawing code here
    base.Draw(gameTime);
}

这是 VS2019 profiler 记录的内容

下一个例子我只在程序开始时初始化string[]测试

public Game1()
{
    _graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
    IsMouseVisible = true;
    string[] test = new string[100000];
}

这是 VS2019 profiler 记录的内容

所以从这个例子来看,我似乎从来不想在运行时初始化变量,而是尝试在程序开始时初始化所有变量。我将需要 re-use 相同的变量以避免垃圾收集器冻结游戏。谢谢大家回复。

只是想告诉大家冻结实际上不是由于垃圾收集,实际上是因为这行代码。

if (pingDelay > 3)
{
   ping = Int32.Parse(pingSender.Send(client.ServerConnection.RemoteEndPoint.Address, 120, Encoding.ASCII.GetBytes(“0”), options).RoundtripTime.ToString());
   pingDelay = 0;
}

pingSender.Send() 应该是异步的。因为不是它在等待服务器 return 结果时冻结了游戏。