Skip to content

Commit bbe1729

Browse files
committed
Overhaul UIDataProvider
1 parent 804dd48 commit bbe1729

10 files changed

+179
-82
lines changed

src/MHServerEmu.Games/Regions/RegionArchive.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public RegionArchive(ulong replicationId)
3333
ReplicationPolicy = AOINetworkPolicyValues.DefaultPolicy;
3434
Properties = new(replicationId);
3535
MissionManager = new();
36-
UIDataProvider = new(Array.Empty<UISyncData>());
36+
UIDataProvider = new();
3737
ObjectiveGraph = new();
3838
}
3939

src/MHServerEmu.Games/Regions/RegionManager.cs

+14-11
Original file line numberDiff line numberDiff line change
@@ -549,17 +549,20 @@ private static byte[] GetArchiveData(RegionPrototypeId prototype)
549549

550550
#endregion
551551

552-
archive.UIDataProvider = new(new UISyncData[] {
553-
// Widget: UI/MetaGame/MissionName.prototype
554-
// Context: Missions/Prototypes/PVEEndgame/PatrolMidtown/Events/MidtownEventMegaSentinel.prototype
555-
new UIWidgetMissionText((PrototypeId)7164846210465729875, (PrototypeId)10490887443555427166, Array.Empty<PrototypeId>(),
556-
(LocaleStringId)8188822000559654203, LocaleStringId.Invalid),
557-
558-
// Widget: UI/MetaGame/TimeRemainingStoryMode2.prototype
559-
// Context: Missions/Prototypes/PVEEndgame/PatrolMidtown/Events/MidtownEventMegaSentinel.prototype
560-
new UIWidgetGenericFraction((PrototypeId)11932510257277768241, (PrototypeId)10490887443555427166, Array.Empty<PrototypeId>(),
561-
1, 1, 0, (long)Clock.GameTime.TotalMilliseconds + 251550, false) // 161351934500 (Currentservergametime) - 161351682950 = 251550
562-
});
552+
// Widget: UI/MetaGame/MissionName.prototype
553+
// Context: Missions/Prototypes/PVEEndgame/PatrolMidtown/Events/MidtownEventMegaSentinel.prototype
554+
var missionTextWidget = archive.UIDataProvider.GetWidget<UIWidgetMissionText>((PrototypeId)7164846210465729875, (PrototypeId)10490887443555427166);
555+
missionTextWidget.MissionName = (LocaleStringId)8188822000559654203;
556+
missionTextWidget.MissionObjectiveName = LocaleStringId.Invalid;
557+
558+
// Widget: UI/MetaGame/TimeRemainingStoryMode2.prototype
559+
// Context: Missions/Prototypes/PVEEndgame/PatrolMidtown/Events/MidtownEventMegaSentinel.prototype
560+
var genericFractionWidget = archive.UIDataProvider.GetWidget<UIWidgetGenericFraction>((PrototypeId)11932510257277768241, (PrototypeId)10490887443555427166);
561+
genericFractionWidget.CurrentCount = 1;
562+
genericFractionWidget.TotalCount = 1;
563+
genericFractionWidget.TimeStart = 0;
564+
genericFractionWidget.TimeEnd = (long)Clock.GameTime.TotalMilliseconds + 251550;
565+
genericFractionWidget.TimePaused = false;
563566

564567
break;
565568
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace MHServerEmu.Games.UI
2+
{
3+
/// <summary>
4+
/// Exposes a <see cref="UI.UIDataProvider"/> instance.
5+
/// </summary>
6+
public interface IUIDataProviderOwner
7+
{
8+
public UIDataProvider UIDataProvider { get; }
9+
}
10+
}
+96-45
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,134 @@
11
using System.Text;
2+
using Gazillion;
23
using Google.ProtocolBuffers;
3-
using MHServerEmu.Core.Extensions;
4+
using MHServerEmu.Core.Logging;
45
using MHServerEmu.Core.Serialization;
56
using MHServerEmu.Games.Common;
67
using MHServerEmu.Games.GameData;
78
using MHServerEmu.Games.GameData.Prototypes;
9+
using MHServerEmu.Games.Regions;
810
using MHServerEmu.Games.UI.Widgets;
911

1012
namespace MHServerEmu.Games.UI
1113
{
1214
public class UIDataProvider
1315
{
14-
public UISyncData[] UISyncData { get; set; }
16+
private static readonly Logger Logger = LogManager.CreateLogger();
1517

16-
public UIDataProvider(UISyncData[] uiSyncData)
18+
private readonly Dictionary<(PrototypeId, PrototypeId), UISyncData> _dataDict = new();
19+
20+
public Game Game { get; }
21+
public Region Region { get; }
22+
23+
public UIDataProvider() { }
24+
25+
public UIDataProvider(CodedInputStream stream, BoolDecoder boolDecoder)
1726
{
18-
UISyncData = uiSyncData;
27+
ulong numWidgets = stream.ReadRawVarint64();
28+
29+
for (ulong i = 0; i < numWidgets; i++)
30+
{
31+
PrototypeId widgetRef = stream.ReadPrototypeRef<Prototype>();
32+
PrototypeId contextRef = stream.ReadPrototypeRef<Prototype>();
33+
UpdateOrCreateUIWidget(widgetRef, contextRef, stream, boolDecoder);
34+
}
1935
}
2036

21-
public UIDataProvider(CodedInputStream stream, BoolDecoder boolDecoder)
37+
public void Encode(CodedOutputStream stream, BoolEncoder boolEncoder)
2238
{
23-
UISyncData = new UISyncData[stream.ReadRawVarint32()];
24-
for (int i = 0; i < UISyncData.Length; i++)
39+
stream.WriteRawVarint64((ulong)_dataDict.Count);
40+
foreach (var kvp in _dataDict)
2541
{
26-
PrototypeId widgetR = stream.ReadPrototypeRef<Prototype>();
27-
PrototypeId contextR = stream.ReadPrototypeRef<Prototype>();
42+
stream.WritePrototypeRef<Prototype>(kvp.Key.Item1);
43+
stream.WritePrototypeRef<Prototype>(kvp.Key.Item2);
44+
kvp.Value.Encode(stream, boolEncoder);
45+
}
46+
}
2847

29-
PrototypeId[] areas = new PrototypeId[stream.ReadRawInt32()];
30-
for (int j = 0; j < areas.Length; j++)
31-
areas[j] = stream.ReadPrototypeRef<Prototype>();
48+
public void EncodeBools(BoolEncoder boolEncoder)
49+
{
50+
foreach (UISyncData uiData in _dataDict.Values)
51+
uiData.EncodeBools(boolEncoder);
52+
}
3253

33-
string className = GameDatabase.DataDirectory.GetPrototypeBlueprint(widgetR).RuntimeBindingClassType.Name;
54+
public override string ToString()
55+
{
56+
StringBuilder sb = new();
3457

35-
switch (className)
36-
{
37-
case "UIWidgetButtonPrototype":
38-
UISyncData[i] = new UIWidgetButton(widgetR, contextR, areas, stream);
39-
break;
58+
foreach (var kvp in _dataDict)
59+
{
60+
string widgetName = GameDatabase.GetFormattedPrototypeName(kvp.Key.Item1);
61+
string contextName = GameDatabase.GetFormattedPrototypeName(kvp.Key.Item2);
62+
sb.AppendLine($"_dataDict[{widgetName}][{contextName}]: {kvp.Value}");
63+
}
4064

41-
case "UIWidgetEntityIconsSyncDataPrototype":
42-
UISyncData[i] = new UIWidgetEntityIconsSyncData(widgetR, contextR, areas, stream, boolDecoder);
43-
break;
65+
return sb.ToString();
66+
}
4467

45-
case "UIWidgetGenericFractionPrototype":
46-
UISyncData[i] = new UIWidgetGenericFraction(widgetR, contextR, areas, stream, boolDecoder);
47-
break;
68+
public T GetWidget<T>(PrototypeId widgetRef, PrototypeId contextRef) where T: UISyncData
69+
{
70+
if (_dataDict.TryGetValue((widgetRef, contextRef), out UISyncData widget) == false)
71+
widget = AllocateUIWidget(widgetRef, contextRef);
4872

49-
case "UIWidgetMissionTextPrototype":
50-
UISyncData[i] = new UIWidgetMissionText(widgetR, contextR, areas, stream);
51-
break;
73+
return widget as T;
74+
}
5275

53-
case "UIWidgetReadyCheckPrototype":
54-
UISyncData[i] = new UIWidgetReadyCheck(widgetR, contextR, areas, stream);
55-
break;
76+
public void DeleteWidget(PrototypeId widgetRef, PrototypeId contextRef)
77+
{
78+
if (_dataDict.Remove((widgetRef, contextRef)) == false)
79+
Logger.Warn($"DeleteWidget(): Widget not found, widgetRef={widgetRef}, contextRef={contextRef}");
5680

57-
default:
58-
throw new($"Unsupported UISyncData type {className}.");
59-
}
60-
}
81+
// todo: send a NetMessageUISyncDataRemove to clients when a widget is removed server-side
6182
}
6283

63-
public void Encode(CodedOutputStream stream, BoolEncoder boolEncoder)
84+
public void DeleteWidget(NetMessageUISyncDataRemove removeMessage)
6485
{
65-
stream.WriteRawVarint32((uint)UISyncData.Length);
66-
foreach (UISyncData data in UISyncData)
67-
data.Encode(stream, boolEncoder);
86+
throw new NotImplementedException();
6887
}
6988

70-
public void EncodeBools(BoolEncoder boolEncoder)
89+
/// <summary>
90+
/// Creates a <see cref="UISyncData"/> inst
91+
/// </summary>
92+
private UISyncData AllocateUIWidget(PrototypeId widgetRef, PrototypeId contextRef)
7193
{
72-
foreach (UISyncData data in UISyncData)
73-
data.EncodeBools(boolEncoder);
94+
if (widgetRef == PrototypeId.Invalid)
95+
return Logger.WarnReturn<UISyncData>(null, "AllocateUIWidget(): widgetRef == PrototypeId.Invalid");
96+
97+
if (_dataDict.ContainsKey((widgetRef, contextRef)))
98+
return Logger.WarnReturn<UISyncData>(null, $"AllocateUIWidget(): Widget already exists, widgetRef={widgetRef}, contextRef={contextRef}");
99+
100+
MetaGameDataPrototype metaGameDataPrototype = GameDatabase.GetPrototype<MetaGameDataPrototype>(widgetRef);
101+
102+
if (metaGameDataPrototype == null)
103+
return Logger.WarnReturn<UISyncData>(null, "AllocateUIWidget(): metaGameDataPrototype == null");
104+
105+
UISyncData uiSyncData = metaGameDataPrototype switch
106+
{
107+
UIWidgetButtonPrototype => new UIWidgetButton(this, widgetRef, contextRef),
108+
UIWidgetEntityIconsPrototype => new UIWidgetEntityIconsSyncData(this, widgetRef, contextRef),
109+
UIWidgetGenericFractionPrototype => new UIWidgetGenericFraction(this, widgetRef, contextRef),
110+
UIWidgetMissionTextPrototype => new UIWidgetMissionText(this, widgetRef, contextRef),
111+
UIWidgetReadyCheckPrototype => new UIWidgetReadyCheck(this, widgetRef, contextRef),
112+
_ => null,
113+
};
114+
115+
if (uiSyncData == null)
116+
return Logger.WarnReturn<UISyncData>(null, "AllocateUIWidget(): Trying to allocate widget of the base type");
117+
118+
_dataDict.Add((widgetRef, contextRef), uiSyncData);
119+
return uiSyncData;
74120
}
75121

76-
public override string ToString()
122+
// TODO: stream + decoder -> archive
123+
private UISyncData UpdateOrCreateUIWidget(PrototypeId widgetRef, PrototypeId contextRef, CodedInputStream stream, BoolDecoder boolDecoder)
77124
{
78-
StringBuilder sb = new();
79-
for (int i = 0; i < UISyncData.Length; i++) sb.AppendLine($"UISyncData{i}: {UISyncData[i]}");
80-
return sb.ToString();
125+
if (_dataDict.TryGetValue((widgetRef, contextRef), out UISyncData uiData) == false)
126+
uiData = AllocateUIWidget(widgetRef, contextRef);
127+
128+
uiData.Decode(stream, boolDecoder);
129+
uiData.UpdateUI();
130+
131+
return uiData;
81132
}
82133
}
83134
}

src/MHServerEmu.Games/UI/UISyncData.cs

+23-16
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,37 @@ namespace MHServerEmu.Games.UI
1010
{
1111
public class UISyncData
1212
{
13-
public PrototypeId WidgetR { get; set; }
14-
public PrototypeId ContextR { get; set; }
15-
public PrototypeId[] Areas { get; set; }
13+
protected readonly UIDataProvider _uiDataProvider;
14+
protected readonly PrototypeId _widgetRef;
15+
protected readonly PrototypeId _contextRef;
1616

17-
public UISyncData(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas)
17+
protected PrototypeId[] _areas = Array.Empty<PrototypeId>();
18+
19+
public UISyncData(UIDataProvider uiDataProvider, PrototypeId widgetRef, PrototypeId contextRef)
1820
{
19-
WidgetR = widgetR;
20-
ContextR = contextR;
21-
Areas = areas;
21+
_uiDataProvider = uiDataProvider;
22+
_widgetRef = widgetRef;
23+
_contextRef = contextRef;
2224
}
2325

24-
public virtual void Encode(CodedOutputStream stream, BoolEncoder boolEncoder)
26+
public virtual void Decode(CodedInputStream stream, BoolDecoder boolDecoder)
2527
{
26-
stream.WritePrototypeRef<Prototype>(WidgetR);
27-
stream.WritePrototypeRef<Prototype>(ContextR);
28+
_areas = new PrototypeId[stream.ReadRawInt32()];
29+
for (int i = 0; i < _areas.Length; i++)
30+
_areas[i] = stream.ReadPrototypeRef<Prototype>();
31+
}
2832

29-
stream.WriteRawInt32(Areas.Length);
30-
for (int i = 0; i < Areas.Length; i++)
31-
stream.WritePrototypeRef<Prototype>(Areas[i]);
33+
public virtual void Encode(CodedOutputStream stream, BoolEncoder boolEncoder)
34+
{
35+
stream.WriteRawInt32(_areas.Length);
36+
for (int i = 0; i < _areas.Length; i++)
37+
stream.WritePrototypeRef<Prototype>(_areas[i]);
3238
}
3339

3440
public virtual void EncodeBools(BoolEncoder boolEncoder) { }
3541

42+
public virtual void UpdateUI() { }
43+
3644
public override string ToString()
3745
{
3846
StringBuilder sb = new();
@@ -42,9 +50,8 @@ public override string ToString()
4250

4351
protected virtual void BuildString(StringBuilder sb)
4452
{
45-
sb.AppendLine($"WidgetR: {GameDatabase.GetPrototypeName(WidgetR)}");
46-
sb.AppendLine($"ContextR: {GameDatabase.GetPrototypeName(ContextR)}");
47-
for (int i = 0; i < Areas.Length; i++) sb.AppendLine($"Area{i}: {Areas[i]}");
53+
for (int i = 0; i < _areas.Length; i++)
54+
sb.AppendLine($"_areas[{i}]: {_areas[i]}");
4855
}
4956
}
5057
}

src/MHServerEmu.Games/UI/Widgets/UIWidgetButton.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ public class UIWidgetButton : UISyncData
99
{
1010
public ulong[] Callbacks { get; set; } // PlayerGuid
1111

12-
public UIWidgetButton(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, CodedInputStream stream) : base(widgetR, contextR, areas)
12+
public UIWidgetButton(UIDataProvider uiDataProvider, PrototypeId widgetRef, PrototypeId contextRef) : base(uiDataProvider, widgetRef, contextRef) { }
13+
14+
public override void Decode(CodedInputStream stream, BoolDecoder boolDecoder)
1315
{
16+
base.Decode(stream, boolDecoder);
17+
1418
Callbacks = new ulong[stream.ReadRawVarint64()];
1519
for (int i = 0; i < Callbacks.Length; i++)
1620
Callbacks[i] = stream.ReadRawVarint64();
@@ -29,7 +33,8 @@ protected override void BuildString(StringBuilder sb)
2933
{
3034
base.BuildString(sb);
3135

32-
for (int i = 0; i < Callbacks.Length; i++) sb.AppendLine($"Callback{i}: {Callbacks[i]}");
36+
for (int i = 0; i < Callbacks.Length; i++)
37+
sb.AppendLine($"Callback{i}: {Callbacks[i]}");
3338
}
3439
}
3540
}

src/MHServerEmu.Games/UI/Widgets/UIWidgetEntityIconsSyncData.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ public class UIWidgetEntityIconsSyncData : UISyncData
1010
{
1111
public FilterEntry[] FilterEntries { get; set; }
1212

13-
public UIWidgetEntityIconsSyncData(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, CodedInputStream stream, BoolDecoder boolDecoder) : base(widgetR, contextR, areas)
13+
public UIWidgetEntityIconsSyncData(UIDataProvider uiDataProvider, PrototypeId widgetRef, PrototypeId contextRef) : base(uiDataProvider, widgetRef, contextRef) { }
14+
15+
public override void Decode(CodedInputStream stream, BoolDecoder boolDecoder)
1416
{
17+
base.Decode(stream, boolDecoder);
18+
1519
FilterEntries = new FilterEntry[stream.ReadRawVarint64()];
1620
for (int i = 0; i < FilterEntries.Length; i++)
1721
FilterEntries[i] = new(stream, boolDecoder);
@@ -36,7 +40,8 @@ protected override void BuildString(StringBuilder sb)
3640
{
3741
base.BuildString(sb);
3842

39-
for (int i = 0; i < FilterEntries.Length; i++) sb.AppendLine($"FilterEntry{i}: {FilterEntries[i]}");
43+
for (int i = 0; i < FilterEntries.Length; i++)
44+
sb.AppendLine($"FilterEntry{i}: {FilterEntries[i]}");
4045
}
4146
}
4247

src/MHServerEmu.Games/UI/Widgets/UIWidgetGenericFraction.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ public class UIWidgetGenericFraction : UISyncData
1515
public bool TimePaused { get; set; }
1616

1717
public UIWidgetGenericFraction(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas,
18-
int currentCount, int totalCount, long timeStart, long timeEnd, bool timePaused) : base(widgetR, contextR, areas)
18+
int currentCount, int totalCount, long timeStart, long timeEnd, bool timePaused) : base(null, widgetR, contextR)
1919
{
20+
_areas = areas;
21+
2022
CurrentCount = currentCount;
2123
TotalCount = totalCount;
2224
TimeStart = timeStart;
2325
TimeEnd = timeEnd;
2426
TimePaused = timePaused;
2527
}
2628

27-
public UIWidgetGenericFraction(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, CodedInputStream stream, BoolDecoder boolDecoder) : base(widgetR, contextR, areas)
29+
public UIWidgetGenericFraction(UIDataProvider uiDataProvider, PrototypeId widgetRef, PrototypeId contextRef) : base(uiDataProvider, widgetRef, contextRef) { }
30+
31+
public override void Decode(CodedInputStream stream, BoolDecoder boolDecoder)
2832
{
33+
base.Decode(stream, boolDecoder);
34+
2935
CurrentCount = stream.ReadRawInt32();
3036
TotalCount = stream.ReadRawInt32();
3137
TimeStart = stream.ReadRawInt64();

src/MHServerEmu.Games/UI/Widgets/UIWidgetMissionText.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ public class UIWidgetMissionText : UISyncData
1010
public LocaleStringId MissionName { get; set; }
1111
public LocaleStringId MissionObjectiveName { get; set; }
1212

13-
public UIWidgetMissionText(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, LocaleStringId missionName, LocaleStringId missionObjectiveName) : base(widgetR, contextR, areas)
13+
public UIWidgetMissionText(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, LocaleStringId missionName, LocaleStringId missionObjectiveName) : base(null, widgetR, contextR)
1414
{
15+
_areas = areas;
16+
1517
MissionName = missionName;
1618
MissionObjectiveName = missionObjectiveName;
1719
}
1820

19-
public UIWidgetMissionText(PrototypeId widgetR, PrototypeId contextR, PrototypeId[] areas, CodedInputStream stream) : base(widgetR, contextR, areas)
21+
public UIWidgetMissionText(UIDataProvider uiDataProvider, PrototypeId widgetRef, PrototypeId contextRef) : base(uiDataProvider, widgetRef, contextRef) { }
22+
23+
public override void Decode(CodedInputStream stream, BoolDecoder boolDecoder)
2024
{
25+
base.Decode(stream, boolDecoder);
26+
2127
MissionName = (LocaleStringId)stream.ReadRawVarint64();
2228
MissionObjectiveName = (LocaleStringId)stream.ReadRawVarint64();
2329
}

0 commit comments

Comments
 (0)