mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 17:47:24 +02:00
260 lines
8.7 KiB
C#
260 lines
8.7 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.ViewVariables.Editors;
|
|
using Robust.Client.ViewVariables.Instances;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
|
|
|
namespace Robust.Client.ViewVariables.Traits
|
|
{
|
|
internal sealed class ViewVariablesTraitEnumerable : ViewVariablesTrait
|
|
{
|
|
private const int ElementsPerPage = 25;
|
|
private readonly List<object?> _cache = new();
|
|
private int _page;
|
|
private IEnumerator? _enumerator;
|
|
private bool _ended;
|
|
private bool _networked;
|
|
|
|
private Button _leftButton = default!;
|
|
private Button _rightButton = default!;
|
|
private LineEdit _pageLabel = default!;
|
|
private BoxContainer _controlsHBox = default!;
|
|
private BoxContainer _elementsVBox = default!;
|
|
|
|
private int HighestKnownPage => Math.Max(0, ((_cache.Count + ElementsPerPage - 1) / ElementsPerPage) - 1);
|
|
|
|
private SemaphoreSlim? _networkSemaphore;
|
|
|
|
public override void Initialize(ViewVariablesInstanceObject instance)
|
|
{
|
|
base.Initialize(instance);
|
|
if (instance.Object == null)
|
|
{
|
|
DebugTools.Assert(instance.Session != null);
|
|
_networked = true;
|
|
_networkSemaphore = new SemaphoreSlim(1, 1);
|
|
}
|
|
else
|
|
{
|
|
var enumerable = (IEnumerable) instance.Object;
|
|
_enumerator = enumerable.GetEnumerator();
|
|
}
|
|
|
|
var outerVBox = new BoxContainer
|
|
{
|
|
Orientation = LayoutOrientation.Vertical
|
|
};
|
|
_controlsHBox = new BoxContainer
|
|
{
|
|
Orientation = LayoutOrientation.Horizontal,
|
|
HorizontalAlignment = Control.HAlignment.Center
|
|
};
|
|
|
|
{
|
|
// Page navigational controls.
|
|
_leftButton = new Button {Text = "<<", Disabled = true};
|
|
_pageLabel = new LineEdit {Text = "0", MinSize = new(60, 0)};
|
|
_rightButton = new Button {Text = ">>"};
|
|
|
|
_leftButton.OnPressed += _leftButtonPressed;
|
|
_pageLabel.OnTextEntered += _lineEditTextEntered;
|
|
_rightButton.OnPressed += _rightButtonPressed;
|
|
|
|
_controlsHBox.AddChild(_leftButton);
|
|
_controlsHBox.AddChild(_pageLabel);
|
|
_controlsHBox.AddChild(_rightButton);
|
|
}
|
|
|
|
outerVBox.AddChild(_controlsHBox);
|
|
|
|
_elementsVBox = new BoxContainer
|
|
{
|
|
Orientation = LayoutOrientation.Vertical
|
|
};
|
|
outerVBox.AddChild(_elementsVBox);
|
|
|
|
instance.AddTab("IEnumerable", outerVBox);
|
|
}
|
|
|
|
public override async void Refresh()
|
|
{
|
|
_cache.Clear();
|
|
_ended = false;
|
|
if (_networked)
|
|
{
|
|
await _cacheTo(1, true);
|
|
}
|
|
else
|
|
{
|
|
var enumerable = (IEnumerable) Instance.Object!;
|
|
_enumerator = enumerable.GetEnumerator();
|
|
}
|
|
|
|
await _moveToPage(_page);
|
|
}
|
|
|
|
private async void _lineEditTextEntered(LineEdit.LineEditEventArgs obj)
|
|
{
|
|
await _moveToPage(int.Parse(obj.Text, CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
private async void _rightButtonPressed(BaseButton.ButtonEventArgs obj)
|
|
{
|
|
await _moveToPage(_page + 1);
|
|
}
|
|
|
|
private async void _leftButtonPressed(BaseButton.ButtonEventArgs obj)
|
|
{
|
|
await _moveToPage(_page - 1);
|
|
}
|
|
|
|
private async Task _moveToPage(int page)
|
|
{
|
|
// TODO: Network overhead optimization potential:
|
|
// Right now, (in NETWORK mode) if I request page 5, it has to cache all 5 pages,
|
|
// now the server obviously (enumerator and all that) has to TOO, but whatever.
|
|
// The waste is that all pages are also SENT, even though we only really care about the fifth at the moment.
|
|
// Because the cache can't have holes (and also the network system is too simplistic at the moment,
|
|
// if you do do a by-page pull and you're way too far along,
|
|
// you'll just get 0 elements which doesn't tell you where it ended but that's kinda necessary.
|
|
if (page < 0)
|
|
{
|
|
page = 0;
|
|
}
|
|
|
|
if (page > HighestKnownPage || (!_ended && page == HighestKnownPage))
|
|
{
|
|
if (_ended)
|
|
{
|
|
// The requested page is higher than the highest page we have (and we know this because the enumerator ended).
|
|
page = HighestKnownPage;
|
|
}
|
|
else
|
|
{
|
|
// The page is higher than the highest page we have, but the enumerator hasn't ended yet so that might be valid.
|
|
// Gotta get more data.
|
|
await _cacheTo((page + 1) * ElementsPerPage);
|
|
|
|
if (page > HighestKnownPage)
|
|
{
|
|
// We tried, but the enumerator ended before we reached our goal.
|
|
// Oh well.
|
|
DebugTools.Assert(_ended);
|
|
page = HighestKnownPage;
|
|
}
|
|
}
|
|
}
|
|
|
|
_elementsVBox.DisposeAllChildren();
|
|
|
|
for (var i = page * ElementsPerPage; i < ElementsPerPage * (page + 1) && i < _cache.Count; i++)
|
|
{
|
|
var element = _cache[i];
|
|
VVPropEditor editor;
|
|
if (element == null)
|
|
{
|
|
editor = new VVPropEditorDummy();
|
|
}
|
|
else
|
|
{
|
|
var type = element.GetType();
|
|
editor = Instance.ViewVariablesManager.PropertyFor(type);
|
|
}
|
|
|
|
var control = editor.Initialize(element, true);
|
|
if (_networked)
|
|
{
|
|
var selectorChain = new object[] {new ViewVariablesEnumerableIndexSelector(i)};
|
|
editor.WireNetworkSelector(Instance.Session!.SessionId, selectorChain);
|
|
}
|
|
|
|
_elementsVBox.AddChild(control);
|
|
}
|
|
|
|
_page = page;
|
|
|
|
_updateControls();
|
|
}
|
|
|
|
private void _updateControls()
|
|
{
|
|
if (_ended && HighestKnownPage == 0)
|
|
{
|
|
_controlsHBox.Visible = false;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
_controlsHBox.Visible = true;
|
|
}
|
|
|
|
|
|
_leftButton.Disabled = _page == 0;
|
|
_pageLabel.Text = $"{_page + 1}";
|
|
_rightButton.Disabled = _page == HighestKnownPage && _ended;
|
|
}
|
|
|
|
private async Task _cacheTo(int index, bool netRefresh=false)
|
|
{
|
|
DebugTools.Assert(_networked || !netRefresh);
|
|
if (index < _cache.Count)
|
|
{
|
|
// This check is probably redundant, oh well.
|
|
return;
|
|
}
|
|
if (_networked)
|
|
{
|
|
await _networkSemaphore!.WaitAsync();
|
|
|
|
try
|
|
{
|
|
// I believe it may theoretically be possible to hit this, so...
|
|
if (index < _cache.Count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Would it maybe be a good idea to send this in chunks?
|
|
// Too lazy to code that right now.
|
|
// Oh well..
|
|
var blob = await Instance.ViewVariablesManager.RequestData<ViewVariablesBlobEnumerable>(Instance.Session!,
|
|
new ViewVariablesRequestEnumerable(_cache.Count, index, netRefresh));
|
|
|
|
_cache.AddRange(blob.Objects);
|
|
|
|
if (_cache.Count < index)
|
|
{
|
|
_ended = true;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_networkSemaphore.Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugTools.AssertNotNull(_enumerator);
|
|
while (_cache.Count < index)
|
|
{
|
|
if (!_enumerator!.MoveNext())
|
|
{
|
|
_ended = true;
|
|
break;
|
|
}
|
|
|
|
_cache.Add(_enumerator!.Current);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|