Add objects replication and RPC stats table to Network Profiler
This commit is contained in:
@@ -429,21 +429,8 @@ namespace FlaxEditor.Windows.Profiler
|
||||
private void UpdateTable(ref ViewRange viewRange)
|
||||
{
|
||||
_table.IsLayoutLocked = true;
|
||||
int idx = 0;
|
||||
while (_table.Children.Count > idx)
|
||||
{
|
||||
var child = _table.Children[idx];
|
||||
if (child is Row row)
|
||||
{
|
||||
_tableRowsCache.Add(row);
|
||||
child.Parent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
RecycleTableRows(_table, _tableRowsCache);
|
||||
UpdateTableInner(ref viewRange);
|
||||
|
||||
_table.UnlockChildrenRecursive();
|
||||
|
||||
@@ -298,21 +298,7 @@ namespace FlaxEditor.Windows.Profiler
|
||||
private void UpdateTable()
|
||||
{
|
||||
_table.IsLayoutLocked = true;
|
||||
int idx = 0;
|
||||
while (_table.Children.Count > idx)
|
||||
{
|
||||
var child = _table.Children[idx];
|
||||
if (child is Row row)
|
||||
{
|
||||
_tableRowsCache.Add(row);
|
||||
child.Parent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
_table.LockChildrenRecursive();
|
||||
RecycleTableRows(_table, _tableRowsCache);
|
||||
|
||||
UpdateTableInner();
|
||||
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class ProfilingTools
|
||||
{
|
||||
partial struct NetworkEventStat
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the event name.
|
||||
/// </summary>
|
||||
public unsafe string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* name = Name0)
|
||||
return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
@@ -13,6 +37,10 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
private readonly SingleChart _dataSentChart;
|
||||
private readonly SingleChart _dataReceivedChart;
|
||||
private readonly Table _tableRpc;
|
||||
private readonly Table _tableRep;
|
||||
private SamplesBuffer<ProfilingTools.NetworkEventStat[]> _events;
|
||||
private List<Row> _tableRowsCache;
|
||||
private FlaxEngine.Networking.NetworkDriverStats _prevStats;
|
||||
|
||||
public Network()
|
||||
@@ -48,11 +76,10 @@ namespace FlaxEditor.Windows.Profiler
|
||||
Parent = layout,
|
||||
};
|
||||
_dataReceivedChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
}
|
||||
|
||||
private static string FormatSampleBytes(float v)
|
||||
{
|
||||
return Utilities.Utils.FormatBytesCount((ulong)v);
|
||||
// Tables
|
||||
_tableRpc = InitTable(layout, "RPC Name");
|
||||
_tableRep = InitTable(layout, "Replication Name");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -60,11 +87,13 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
_dataSentChart.Clear();
|
||||
_dataReceivedChart.Clear();
|
||||
_events?.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
// Gather peer stats
|
||||
var peers = FlaxEngine.Networking.NetworkPeer.Peers;
|
||||
var stats = new FlaxEngine.Networking.NetworkDriverStats();
|
||||
foreach (var peer in peers)
|
||||
@@ -76,6 +105,12 @@ namespace FlaxEditor.Windows.Profiler
|
||||
_dataSentChart.AddSample(Mathf.Max((long)stats.TotalDataSent - (long)_prevStats.TotalDataSent, 0));
|
||||
_dataReceivedChart.AddSample(Mathf.Max((long)stats.TotalDataReceived - (long)_prevStats.TotalDataReceived, 0));
|
||||
_prevStats = stats;
|
||||
|
||||
// Gather network events
|
||||
var events = ProfilingTools.EventsNetwork;
|
||||
if (_events == null)
|
||||
_events = new SamplesBuffer<ProfilingTools.NetworkEventStat[]>();
|
||||
_events.Add(events);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -83,6 +118,159 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
_dataSentChart.SelectedSampleIndex = selectedFrame;
|
||||
_dataReceivedChart.SelectedSampleIndex = selectedFrame;
|
||||
|
||||
// Update events tables
|
||||
if (_events != null)
|
||||
{
|
||||
if (_tableRowsCache == null)
|
||||
_tableRowsCache = new List<Row>();
|
||||
_tableRpc.IsLayoutLocked = true;
|
||||
_tableRep.IsLayoutLocked = true;
|
||||
RecycleTableRows(_tableRpc, _tableRowsCache);
|
||||
RecycleTableRows(_tableRep, _tableRowsCache);
|
||||
|
||||
var events = _events.Get(selectedFrame);
|
||||
var rowCount = Int2.Zero;
|
||||
if (events != null && events.Length != 0)
|
||||
{
|
||||
var rowColor2 = Style.Current.Background * 1.4f;
|
||||
for (int i = 0; i < events.Length; i++)
|
||||
{
|
||||
var e = events[i];
|
||||
var name = e.Name;
|
||||
var isRpc = name.Contains("::", StringComparison.Ordinal);
|
||||
|
||||
Row row;
|
||||
if (_tableRowsCache.Count != 0)
|
||||
{
|
||||
var last = _tableRowsCache.Count - 1;
|
||||
row = _tableRowsCache[last];
|
||||
_tableRowsCache.RemoveAt(last);
|
||||
}
|
||||
else
|
||||
{
|
||||
row = new Row
|
||||
{
|
||||
Values = new object[5],
|
||||
};
|
||||
}
|
||||
{
|
||||
// Name
|
||||
row.Values[0] = name;
|
||||
|
||||
// Count
|
||||
row.Values[1] = (int)e.Count;
|
||||
|
||||
// Data Size
|
||||
row.Values[2] = (int)e.DataSize;
|
||||
|
||||
// Message Size
|
||||
row.Values[3] = (int)e.MessageSize;
|
||||
|
||||
// Receivers
|
||||
row.Values[4] = (float)e.Receivers / (float)e.Count;
|
||||
}
|
||||
|
||||
var table = isRpc ? _tableRpc : _tableRep;
|
||||
row.Width = table.Width;
|
||||
row.BackgroundColor = rowCount[isRpc ? 0 : 1] % 2 == 0 ? rowColor2 : Color.Transparent;
|
||||
row.Parent = table;
|
||||
if (isRpc)
|
||||
rowCount.X++;
|
||||
else
|
||||
rowCount.Y++;
|
||||
}
|
||||
}
|
||||
|
||||
_tableRpc.Visible = rowCount.X != 0;
|
||||
_tableRep.Visible = rowCount.Y != 0;
|
||||
_tableRpc.Children.Sort(SortRows);
|
||||
_tableRep.Children.Sort(SortRows);
|
||||
|
||||
_tableRpc.UnlockChildrenRecursive();
|
||||
_tableRpc.PerformLayout();
|
||||
_tableRep.UnlockChildrenRecursive();
|
||||
_tableRep.PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_tableRowsCache?.Clear();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private static Table InitTable(ContainerControl parent, string name)
|
||||
{
|
||||
var headerColor = Style.Current.LightBackground;
|
||||
var table = new Table
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new ColumnDefinition
|
||||
{
|
||||
UseExpandCollapseMode = true,
|
||||
CellAlignment = TextAlignment.Near,
|
||||
Title = name,
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Count",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Data Size",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellBytes,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Message Size",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellBytes,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Receivers",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
},
|
||||
Splits = new[]
|
||||
{
|
||||
0.40f,
|
||||
0.15f,
|
||||
0.15f,
|
||||
0.15f,
|
||||
0.15f,
|
||||
},
|
||||
Parent = parent,
|
||||
};
|
||||
return table;
|
||||
}
|
||||
|
||||
private static string FormatSampleBytes(float v)
|
||||
{
|
||||
return Utilities.Utils.FormatBytesCount((ulong)v);
|
||||
}
|
||||
|
||||
private static string FormatCellBytes(object x)
|
||||
{
|
||||
return Utilities.Utils.FormatBytesCount((int)x);
|
||||
}
|
||||
|
||||
private static int SortRows(Control x, Control y)
|
||||
{
|
||||
if (x is Row xRow && y is Row yRow)
|
||||
{
|
||||
var xDataSize = (int)xRow.Values[2];
|
||||
var yDataSize = (int)yRow.Values[2];
|
||||
return yDataSize - xDataSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEngine;
|
||||
|
||||
@@ -135,5 +137,28 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
SelectedSampleChanged?.Invoke(frameIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recycles all table rows to be reused.
|
||||
/// </summary>
|
||||
/// <param name="table">The table.</param>
|
||||
/// <param name="rowsCache">The output cache.</param>
|
||||
protected static void RecycleTableRows(Table table, List<Row> rowsCache)
|
||||
{
|
||||
int idx = 0;
|
||||
while (table.Children.Count > idx)
|
||||
{
|
||||
var child = table.Children[idx];
|
||||
if (child is Row row)
|
||||
{
|
||||
rowsCache.Add(row);
|
||||
child.Parent = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ namespace FlaxEditor.Windows.Profiler
|
||||
/// <returns>The sample value</returns>
|
||||
public T Get(int index)
|
||||
{
|
||||
if (index >= _data.Length || _data.Length == 0)
|
||||
return default;
|
||||
return index == -1 ? _data[_count - 1] : _data[index];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#if COMPILE_WITH_PROFILER
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#endif
|
||||
|
||||
enum class NetworkMessageIDs : uint8
|
||||
{
|
||||
@@ -35,4 +38,22 @@ public:
|
||||
static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
|
||||
struct ProfilerEvent
|
||||
{
|
||||
uint16 Count = 0;
|
||||
uint16 DataSize = 0;
|
||||
uint16 MessageSize = 0;
|
||||
uint16 Receivers = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Enables network usage profiling tools. Captures network objects replication and RPCs send statistics.
|
||||
/// </summary>
|
||||
static bool EnableProfiling;
|
||||
|
||||
static Dictionary<Pair<ScriptingTypeHandle, StringAnsiView>, ProfilerEvent> ProfilerEvents;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -40,6 +40,11 @@ bool NetworkReplicator::EnableLog = false;
|
||||
#define NETWORK_REPLICATOR_LOG(messageType, format, ...)
|
||||
#endif
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
bool NetworkInternal::EnableProfiling = false;
|
||||
Dictionary<Pair<ScriptingTypeHandle, StringAnsiView>, NetworkInternal::ProfilerEvent> NetworkInternal::ProfilerEvents;
|
||||
#endif
|
||||
|
||||
PACK_STRUCT(struct NetworkMessageObjectReplicate
|
||||
{
|
||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicate;
|
||||
@@ -1806,6 +1811,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgData);
|
||||
msg.WriteBytes(stream->GetBuffer(), msgDataSize);
|
||||
uint32 dataSize = msgDataSize, messageSize = msg.Length;
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Unreliable, msg);
|
||||
else
|
||||
@@ -1824,6 +1830,8 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgDataPart);
|
||||
msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize);
|
||||
messageSize += msg.Length;
|
||||
dataSize += msgDataPart.PartSize;
|
||||
dataStart += msgDataPart.PartSize;
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Unreliable, msg);
|
||||
@@ -1832,7 +1840,18 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
}
|
||||
ASSERT_LOW_LAYER(dataStart == size);
|
||||
|
||||
// TODO: stats for bytes send per object type
|
||||
#if COMPILE_WITH_PROFILER
|
||||
// Network stats recording
|
||||
if (EnableProfiling)
|
||||
{
|
||||
const Pair<ScriptingTypeHandle, StringAnsiView> name(obj->GetTypeHandle(), StringAnsiView::Empty);
|
||||
auto& profileEvent = ProfilerEvents[name];
|
||||
profileEvent.Count++;
|
||||
profileEvent.DataSize += dataSize;
|
||||
profileEvent.MessageSize += messageSize;
|
||||
profileEvent.Receivers += isClient ? 1 : CachedTargets.Count();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1873,6 +1892,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgData);
|
||||
msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length());
|
||||
uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0;
|
||||
NetworkChannelType channel = (NetworkChannelType)e.Info.Channel;
|
||||
if (e.Info.Server && isClient)
|
||||
{
|
||||
@@ -1882,13 +1902,27 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString());
|
||||
#endif
|
||||
peer->EndSendMessage(channel, msg);
|
||||
receivers = 1;
|
||||
}
|
||||
else if (e.Info.Client && (isServer || isHost))
|
||||
{
|
||||
// Server -> Client(s)
|
||||
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId);
|
||||
peer->EndSendMessage(channel, msg, CachedTargets);
|
||||
receivers = CachedTargets.Count();
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
// Network stats recording
|
||||
if (EnableProfiling && receivers)
|
||||
{
|
||||
auto& profileEvent = ProfilerEvents[e.Name];
|
||||
profileEvent.Count++;
|
||||
profileEvent.DataSize += dataSize;
|
||||
profileEvent.MessageSize += messageSize;
|
||||
profileEvent.Receivers += receivers;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
RpcQueue.Clear();
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
#if COMPILE_WITH_PROFILER
|
||||
|
||||
#include "ProfilingTools.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Networking/NetworkInternal.h"
|
||||
|
||||
ProfilingTools::MainStats ProfilingTools::Stats;
|
||||
Array<ProfilingTools::ThreadStats, InlinedAllocation<64>> ProfilingTools::EventsCPU;
|
||||
Array<ProfilerGPU::Event> ProfilingTools::EventsGPU;
|
||||
Array<ProfilingTools::NetworkEventStat> ProfilingTools::EventsNetwork;
|
||||
|
||||
class ProfilingToolsService : public EngineService
|
||||
{
|
||||
@@ -120,6 +123,40 @@ void ProfilingToolsService::Update()
|
||||
frame.Extract(ProfilingTools::EventsGPU);
|
||||
}
|
||||
|
||||
// Get the last events from networking runtime
|
||||
{
|
||||
auto& networkEvents = ProfilingTools::EventsNetwork;
|
||||
networkEvents.Resize(NetworkInternal::ProfilerEvents.Count());
|
||||
int32 i = 0;
|
||||
for (const auto& e : NetworkInternal::ProfilerEvents)
|
||||
{
|
||||
const auto& src = e.Value;
|
||||
auto& dst = networkEvents[i++];
|
||||
dst.Count = src.Count;
|
||||
dst.DataSize = src.DataSize;
|
||||
dst.MessageSize = src.MessageSize;
|
||||
dst.Receivers = src.Receivers;
|
||||
const StringAnsiView& typeName = e.Key.First.GetType().Fullname;
|
||||
uint64 len = Math::Min<uint64>(typeName.Length(), ARRAY_COUNT(dst.Name) - 10);
|
||||
Platform::MemoryCopy(dst.Name, typeName.Get(), len);
|
||||
const StringAnsiView& name = e.Key.Second;
|
||||
if (name.HasChars())
|
||||
{
|
||||
uint64 pos = len;
|
||||
dst.Name[pos++] = ':';
|
||||
dst.Name[pos++] = ':';
|
||||
len = Math::Min<uint64>(name.Length(), ARRAY_COUNT(dst.Name) - pos - 1);
|
||||
Platform::MemoryCopy(dst.Name + pos, name.Get(), len);
|
||||
dst.Name[pos + len] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst.Name[len] = 0;
|
||||
}
|
||||
}
|
||||
NetworkInternal::ProfilerEvents.Clear();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Print CPU events to the log
|
||||
{
|
||||
@@ -173,6 +210,7 @@ void ProfilingToolsService::Dispose()
|
||||
ProfilingTools::EventsCPU.Clear();
|
||||
ProfilingTools::EventsCPU.SetCapacity(0);
|
||||
ProfilingTools::EventsGPU.SetCapacity(0);
|
||||
ProfilingTools::EventsNetwork.SetCapacity(0);
|
||||
}
|
||||
|
||||
bool ProfilingTools::GetEnabled()
|
||||
@@ -184,6 +222,7 @@ void ProfilingTools::SetEnabled(bool enabled)
|
||||
{
|
||||
ProfilerCPU::Enabled = enabled;
|
||||
ProfilerGPU::Enabled = enabled;
|
||||
NetworkInternal::EnableProfiling = enabled;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -105,6 +105,24 @@ public:
|
||||
API_FIELD() Array<ProfilerCPU::Event> Events;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The network stat.
|
||||
/// </summary>
|
||||
API_STRUCT(NoDefault) struct NetworkEventStat
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkEventStat);
|
||||
|
||||
// Amount of occurrences.
|
||||
API_FIELD() uint16 Count;
|
||||
// Transferred data size (in bytes).
|
||||
API_FIELD() uint16 DataSize;
|
||||
// Transferred message (data+header) size (in bytes).
|
||||
API_FIELD() uint16 MessageSize;
|
||||
// Amount of peers that will receive this message.
|
||||
API_FIELD() uint16 Receivers;
|
||||
API_FIELD(Private, NoArray) byte Name[120];
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Controls the engine profiler (CPU, GPU, etc.) usage.
|
||||
@@ -130,6 +148,11 @@ public:
|
||||
/// The GPU rendering profiler events.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) static Array<ProfilerGPU::Event> EventsGPU;
|
||||
|
||||
/// <summary>
|
||||
/// The networking profiler events.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) static Array<NetworkEventStat> EventsNetwork;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1767,6 +1767,10 @@ namespace Flax.Build.Bindings
|
||||
// char's are not blittable, store as short instead
|
||||
contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine();
|
||||
}
|
||||
else if (managedType == "byte")
|
||||
{
|
||||
contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user