修复已知问题

This commit is contained in:
SepComet 2026-03-13 14:55:17 +08:00
parent aa44170d56
commit 777c58b812
11 changed files with 346 additions and 97 deletions

View File

@ -100,7 +100,8 @@ namespace GeometryTD.CustomComponent
_runtime.NodeId = nodeId;
_runtime.NodeType = nodeType;
_runtime.SequenceIndex = sequenceIndex;
GameEntry.InventoryGeneration.ConfigureRunContext(runSeed, sequenceIndex);
_runtime.NextDropOrdinal = 0;
_runtime.NextRewardOrdinal = 0;
_runtime.CombatRunResourceStore.InitializeForCombat(level);
for (int i = 0; i < phases.Count; i++)
{
@ -201,7 +202,13 @@ namespace GeometryTD.CustomComponent
enemy,
_runtime.PhaseLoopRuntime.DisplayPhaseIndex,
_coordinator.ResolveCurrentThemeType());
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(context);
int nextDropOrdinal = _runtime.NextDropOrdinal;
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(
context,
_runtime.RunSeed,
_runtime.SequenceIndex,
ref nextDropOrdinal);
_runtime.NextDropOrdinal = nextDropOrdinal;
_runtime.CombatRunResourceStore.AddEnemyDefeatedReward(result.Coin, result.Gold);
_runtime.CombatRunResourceStore.AddEnemyDefeatedLoot(result.LootItem);
}

View File

@ -60,6 +60,8 @@ namespace GeometryTD.CustomComponent
_runtime.NodeId = 0;
_runtime.NodeType = RunNodeType.None;
_runtime.SequenceIndex = -1;
_runtime.NextDropOrdinal = 0;
_runtime.NextRewardOrdinal = 0;
}
public void CleanupAllCombatEntities()

View File

@ -34,5 +34,7 @@ namespace GeometryTD.CustomComponent
public int NodeId { get; set; }
public RunNodeType NodeType { get; set; }
public int SequenceIndex { get; set; }
public int NextDropOrdinal { get; set; }
public int NextRewardOrdinal { get; set; }
}
}

View File

@ -36,6 +36,9 @@ namespace GeometryTD.CustomComponent
CombatSettlementContext settlementContext,
int displayPhaseIndex,
LevelThemeType themeType,
int runSeed,
int sequenceIndex,
ref int nextRewardOrdinal,
RewardSelectFormUseCase rewardSelectFormUseCase,
Action<RewardSelectItemRawData> onRewardSelected,
Action onGiveUp)
@ -48,7 +51,10 @@ namespace GeometryTD.CustomComponent
IReadOnlyList<TowerCompItemData> candidateItems = GameEntry.InventoryGeneration.BuildRewardCandidates(
displayPhaseIndex,
themeType,
RewardSelectDisplayCount);
RewardSelectDisplayCount,
runSeed,
sequenceIndex,
ref nextRewardOrdinal);
if (candidateItems == null || candidateItems.Count <= 0)
{
settlementContext.Flags.ShouldOpenRewardSelection = false;
@ -60,7 +66,7 @@ namespace GeometryTD.CustomComponent
candidateItems,
displayCount: RewardSelectDisplayCount,
refreshCost: 0,
allowRefreshOnce: false,
allowRotateOnce: false,
allowGiveUp: false,
tipText: "基地满血奖励:请选择 1 个组件");

View File

@ -16,16 +16,23 @@ namespace GeometryTD.CustomComponent
}
Coordinator.EnsureRewardSelectFormUseCaseBound();
int nextRewardOrdinal = Runtime.NextRewardOrdinal;
if (!Runtime.CombatSettlementService.TryPrepareRewardSelection(
Runtime.SettlementContext,
Runtime.PhaseLoopRuntime.DisplayPhaseIndex,
Coordinator.ResolveCurrentThemeType(),
Runtime.RunSeed,
Runtime.SequenceIndex,
ref nextRewardOrdinal,
Runtime.RewardSelectFormUseCase,
Coordinator.OnFullBaseHpRewardSelected,
Coordinator.OnFullBaseHpRewardGiveUp))
{
Coordinator.ChangeState(new CombatFinishFormState(Runtime, Coordinator));
return;
}
Runtime.NextRewardOrdinal = nextRewardOrdinal;
}
public override void OnExit()

View File

@ -10,9 +10,17 @@ namespace GeometryTD.CustomComponent
public sealed class DropPoolRoller
{
private const float RarityCurveScalePhase = 30f;
private static readonly RarityType[] OrderedRarities =
{
RarityType.White,
RarityType.Green,
RarityType.Blue,
RarityType.Purple,
RarityType.Red
};
private readonly List<DROutGameDropPool> _eligibleRowBuffer = new();
private readonly Dictionary<RarityType, float> _rarityWeightBuffer = new();
private readonly float[] _rarityWeightBuffer = new float[OrderedRarities.Length];
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
@ -50,9 +58,8 @@ namespace GeometryTD.CustomComponent
int totalWeight = 0;
DROutGameDropPool fallbackRow = null;
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
foreach (var row in _eligibleRowBuffer)
{
DROutGameDropPool row = _eligibleRowBuffer[i];
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
{
continue;
@ -107,9 +114,8 @@ namespace GeometryTD.CustomComponent
{
_eligibleRowBuffer.Clear();
for (int i = 0; i < allRows.Length; i++)
foreach (var row in allRows)
{
DROutGameDropPool row = allRows[i];
if (row == null)
{
continue;
@ -131,15 +137,18 @@ namespace GeometryTD.CustomComponent
private RarityType RollRarity(int displayPhaseIndex, Random random)
{
_rarityWeightBuffer.Clear();
for (int i = 0; i < _rarityWeightBuffer.Length; i++)
{
_rarityWeightBuffer[i] = 0f;
}
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
foreach (var row in _eligibleRowBuffer)
{
DROutGameDropPool row = _eligibleRowBuffer[i];
for (int rarityIndex = (int)RarityType.White; rarityIndex <= (int)RarityType.Red; rarityIndex++)
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = (RarityType)rarityIndex;
RarityType rarity = OrderedRarities[rarityIndex];
if (!IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
continue;
@ -157,22 +166,14 @@ namespace GeometryTD.CustomComponent
continue;
}
float rarityWeight = rowWeight * curveWeight;
if (_rarityWeightBuffer.TryGetValue(rarity, out float existingWeight))
{
_rarityWeightBuffer[rarity] = existingWeight + rarityWeight;
}
else
{
_rarityWeightBuffer[rarity] = rarityWeight;
}
_rarityWeightBuffer[rarityIndex] += rowWeight * curveWeight;
}
}
float totalWeight = 0f;
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
totalWeight += Mathf.Max(0f, pair.Value);
totalWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
}
if (totalWeight <= 0f)
@ -182,18 +183,21 @@ namespace GeometryTD.CustomComponent
float randomWeight = (float)(random.NextDouble() * totalWeight);
float cumulativeWeight = 0f;
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
cumulativeWeight += Mathf.Max(0f, pair.Value);
cumulativeWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
if (randomWeight <= cumulativeWeight)
{
return pair.Key;
return OrderedRarities[rarityIndex];
}
}
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
return pair.Key;
if (_rarityWeightBuffer[rarityIndex] > 0f)
{
return OrderedRarities[rarityIndex];
}
}
return RarityType.None;
@ -201,9 +205,9 @@ namespace GeometryTD.CustomComponent
private static bool IsEligibleAtPhase(DROutGameDropPool row, int displayPhaseIndex)
{
for (int rarityIndex = (int)RarityType.White; rarityIndex <= (int)RarityType.Red; rarityIndex++)
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = (RarityType)rarityIndex;
RarityType rarity = OrderedRarities[rarityIndex];
if (IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
return true;

View File

@ -12,11 +12,6 @@ namespace GeometryTD.CustomComponent
{
public sealed class InventoryGenerationComponent : GameFrameworkComponent
{
private int _runSeed;
private int _nodeSequenceIndex = -1;
private int _nextDropOrdinal;
private int _nextRewardOrdinal;
private readonly List<DRShopPrice> _shopPriceRows = new();
private IDataTable<DRShopPrice> _shopPriceTable;
private IDataTable<DROutGameDropPool> _dropPoolTable;
@ -28,21 +23,17 @@ namespace GeometryTD.CustomComponent
private RewardCandidateBuilder _rewardCandidateBuilder;
private OutGameDropItemBuilder _outGameDropItemBuilder;
public void ConfigureRunContext(int runSeed, int sequenceIndex)
{
_runSeed = runSeed;
_nodeSequenceIndex = sequenceIndex;
_nextDropOrdinal = 0;
_nextRewardOrdinal = 0;
}
public List<GoodsItemRawData> BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1)
{
EnsureShopBuilder();
return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex);
}
public EnemyDropResult ResolveEnemyDrop(in EnemyDropContext context)
public EnemyDropResult ResolveEnemyDrop(
in EnemyDropContext context,
int runSeed,
int sequenceIndex,
ref int nextDropOrdinal)
{
DREnemy enemy = context.Enemy;
if (enemy == null)
@ -56,9 +47,10 @@ namespace GeometryTD.CustomComponent
? Mathf.Clamp01(enemy.DropPercent * 0.01f)
: Mathf.Clamp01(enemy.DropPercent);
int dropOrdinal = AllocateDropOrdinal();
int dropOrdinal = nextDropOrdinal;
nextDropOrdinal++;
InventoryGenerationRandomContext randomContext =
new(_runSeed, _nodeSequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
new(runSeed, sequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
Random random = randomContext.CreateRandom();
if (enemy.DropGold > 0 && dropRate > 0f && random.NextDouble() <= dropRate)
@ -84,15 +76,30 @@ namespace GeometryTD.CustomComponent
public IReadOnlyList<TowerCompItemData> BuildRewardCandidates(
int displayPhaseIndex,
LevelThemeType themeType,
int candidateCount)
int candidateCount,
int runSeed,
int sequenceIndex,
ref int nextRewardOrdinal)
{
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
return rewardCandidateBuilder.BuildCandidates(
int rewardOrdinal = nextRewardOrdinal;
IReadOnlyList<TowerCompItemData> candidates = rewardCandidateBuilder.BuildCandidates(
displayPhaseIndex,
themeType,
candidateCount,
CreateNextRewardRandomContext,
BuildRewardCandidateItem);
nextRewardOrdinal = rewardOrdinal;
return candidates;
InventoryGenerationRandomContext CreateNextRewardRandomContext()
{
return new InventoryGenerationRandomContext(
runSeed,
sequenceIndex,
InventoryTagSourceType.Reward,
rewardOrdinal++);
}
}
private void EnsureShopTables()
@ -205,25 +212,6 @@ namespace GeometryTD.CustomComponent
return EnsureOutGameDropItemBuilder().TryBuildItem(selectedRow, selectedRarity, randomContext, out droppedItem);
}
private int AllocateDropOrdinal()
{
return _nextDropOrdinal++;
}
private int AllocateRewardOrdinal()
{
return _nextRewardOrdinal++;
}
private InventoryGenerationRandomContext CreateNextRewardRandomContext()
{
return new InventoryGenerationRandomContext(
_runSeed,
_nodeSequenceIndex,
InventoryTagSourceType.Reward,
AllocateRewardOrdinal());
}
private TowerCompItemData BuildRewardCandidateItem(
DROutGameDropPool row,
RarityType rarity,

View File

@ -110,7 +110,7 @@ namespace GeometryTD.UI
return;
}
RewardSelectFormRawData nextRawData = _useCase.TryRefresh();
RewardSelectFormRawData nextRawData = _useCase.TryRotateSelection();
if (nextRawData == null)
{
CloseUI();

View File

@ -15,17 +15,20 @@ namespace GeometryTD.UI
private int _displayCount = 3;
private int _refreshCost;
private bool _allowRefreshOnce = true;
private bool _allowRotateOnce = true;
private bool _allowGiveUp = true;
private bool _hasRefreshed;
private bool _hasRotated;
private int _selectionOffset;
private string _tipText = "Select one reward";
// RewardSelectForm keeps a fixed reward pool for the current node.
// The "refresh" action only rotates which slice of that pool is shown so save/load
// can reopen the same node with the exact same reward contents.
public void ConfigureRewardPool(
IReadOnlyList<RewardSelectItemRawData> rewardPool,
int displayCount = 3,
int refreshCost = 0,
bool allowRefreshOnce = true,
bool allowRotateOnce = true,
bool allowGiveUp = true,
string tipText = null)
{
@ -45,10 +48,10 @@ namespace GeometryTD.UI
_displayCount = Mathf.Max(1, displayCount);
_refreshCost = Mathf.Max(0, refreshCost);
_allowRefreshOnce = allowRefreshOnce;
_allowRotateOnce = allowRotateOnce;
_allowGiveUp = allowGiveUp;
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
_hasRefreshed = false;
_hasRotated = false;
_selectionOffset = 0;
_currentModel = null;
}
@ -57,7 +60,7 @@ namespace GeometryTD.UI
IReadOnlyList<TowerCompItemData> rewardCandidates,
int displayCount = 3,
int refreshCost = 0,
bool allowRefreshOnce = true,
bool allowRotateOnce = true,
bool allowGiveUp = true,
string tipText = null)
{
@ -77,10 +80,10 @@ namespace GeometryTD.UI
_displayCount = Mathf.Max(1, displayCount);
_refreshCost = Mathf.Max(0, refreshCost);
_allowRefreshOnce = allowRefreshOnce;
_allowRotateOnce = allowRotateOnce;
_allowGiveUp = allowGiveUp;
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
_hasRefreshed = false;
_hasRotated = false;
_selectionOffset = 0;
_currentModel = null;
}
@ -93,30 +96,30 @@ namespace GeometryTD.UI
public RewardSelectFormRawData CreateInitialModel()
{
_hasRefreshed = false;
_hasRotated = false;
_currentModel = BuildModel();
return _currentModel;
}
public RewardSelectFormRawData TryRefresh()
public RewardSelectFormRawData TryRotateSelection()
{
if (_currentModel == null)
{
return null;
}
if (!CanRefreshInternal())
if (!CanRotateSelectionInternal())
{
return _currentModel;
}
if (!TryConsumeRefreshCost())
if (!TryConsumeRotateCost())
{
_currentModel.CanRefresh = false;
return _currentModel;
}
_hasRefreshed = true;
_hasRotated = true;
if (_rewardPool.Count > 0)
{
_selectionOffset = (_selectionOffset + _displayCount) % _rewardPool.Count;
@ -166,7 +169,7 @@ namespace GeometryTD.UI
TipText = _tipText,
RewardItems = selectedRewards,
RefreshCost = _refreshCost,
CanRefresh = selectedRewards.Length > 0 && CanRefreshInternal(),
CanRefresh = selectedRewards.Length > 0 && CanRotateSelectionInternal(),
CanGiveUp = _allowGiveUp
};
}
@ -190,17 +193,17 @@ namespace GeometryTD.UI
return results;
}
private bool CanRefreshInternal()
private bool CanRotateSelectionInternal()
{
if (!_allowRefreshOnce || _hasRefreshed)
if (!_allowRotateOnce || _hasRotated)
{
return false;
}
return CanPayRefreshCost();
return CanPayRotateCost();
}
private bool CanPayRefreshCost()
private bool CanPayRotateCost()
{
if (_refreshCost <= 0)
{
@ -215,7 +218,7 @@ namespace GeometryTD.UI
return GameEntry.PlayerInventory.Gold >= _refreshCost;
}
private bool TryConsumeRefreshCost()
private bool TryConsumeRotateCost()
{
if (_refreshCost <= 0)
{

View File

@ -183,11 +183,15 @@ namespace GeometryTD.Tests.EditMode
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
CombatSettlementContext settlementContext = new CombatSettlementContext();
settlementContext.Flags.ShouldOpenRewardSelection = true;
int nextRewardOrdinal = 0;
bool prepared = new CombatSettlementService().TryPrepareRewardSelection(
settlementContext,
displayPhaseIndex: 1,
themeType: LevelThemeType.Plain,
runSeed: 1001,
sequenceIndex: 3,
ref nextRewardOrdinal,
rewardSelectFormUseCase: null,
onRewardSelected: _ => { },
onGiveUp: null);

View File

@ -110,7 +110,43 @@ namespace GeometryTD.Tests.EditMode
}
[Test]
public void RewardSelectFormUseCase_Uses_Stable_Order_And_Deterministic_Refresh_Rotation()
public void DropPoolRoller_Is_Reproducible_When_DropPool_Row_Order_Changes()
{
DROutGameDropPool whiteRow = CreateDropPoolRow(1, "MuzzleComp", 1, "[100,0,0,0,0]");
DROutGameDropPool greenRow = CreateDropPoolRow(2, "BearingComp", 1, "[0,100,0,0,0]");
DROutGameDropPool blueRow = CreateDropPoolRow(3, "BaseComp", 1, "[0,0,100,0,0]");
DROutGameDropPool purpleRow = CreateDropPoolRow(4, "MuzzleComp", 2, "[0,0,0,100,0]");
DROutGameDropPool redRow = CreateDropPoolRow(5, "BearingComp", 2, "[0,0,0,0,100]");
DropPoolRoller forwardOrderRoller = new DropPoolRoller(
new InsertionOrderDataTable<DROutGameDropPool>(whiteRow, greenRow, blueRow, purpleRow, redRow));
DropPoolRoller reverseOrderRoller = new DropPoolRoller(
new InsertionOrderDataTable<DROutGameDropPool>(redRow, purpleRow, blueRow, greenRow, whiteRow));
for (int seed = 1; seed <= 64; seed++)
{
bool forwardRolled = forwardOrderRoller.TryRollRow(
8,
LevelThemeType.Plain,
new System.Random(seed),
out DROutGameDropPool forwardRow,
out RarityType forwardRarity);
bool reverseRolled = reverseOrderRoller.TryRollRow(
8,
LevelThemeType.Plain,
new System.Random(seed),
out DROutGameDropPool reverseRow,
out RarityType reverseRarity);
Assert.That(forwardRolled, Is.True, $"seed={seed}");
Assert.That(reverseRolled, Is.True, $"seed={seed}");
Assert.That(reverseRarity, Is.EqualTo(forwardRarity), $"seed={seed}");
Assert.That(reverseRow.Id, Is.EqualTo(forwardRow.Id), $"seed={seed}");
}
}
[Test]
public void RewardSelectFormUseCase_Uses_Stable_Order_And_Deterministic_Selection_Rotation()
{
RewardSelectFormUseCase useCase = new RewardSelectFormUseCase();
useCase.ConfigureRewardPool(
@ -123,11 +159,11 @@ namespace GeometryTD.Tests.EditMode
},
displayCount: 3,
refreshCost: 0,
allowRefreshOnce: true,
allowRotateOnce: true,
allowGiveUp: false);
RewardSelectFormRawData initialModel = useCase.CreateInitialModel();
RewardSelectFormRawData refreshedModel = useCase.TryRefresh();
RewardSelectFormRawData refreshedModel = useCase.TryRotateSelection();
Assert.That(initialModel.RewardItems.Select(item => item.Title).ToArray(), Is.EqualTo(new[] { "一号", "二号", "三号" }));
Assert.That(refreshedModel.RewardItems.Select(item => item.Title).ToArray(), Is.EqualTo(new[] { "四号", "一号", "二号" }));
@ -141,15 +177,25 @@ namespace GeometryTD.Tests.EditMode
private string BuildEnemyDropSignature(int runSeed, int sequenceIndex)
{
_component.ConfigureRunContext(runSeed, sequenceIndex);
EnemyDropResult result = _component.ResolveEnemyDrop(new EnemyDropContext(CreateEnemyRow(), 8, LevelThemeType.Plain));
int nextDropOrdinal = 0;
EnemyDropResult result = _component.ResolveEnemyDrop(
new EnemyDropContext(CreateEnemyRow(), 8, LevelThemeType.Plain),
runSeed,
sequenceIndex,
ref nextDropOrdinal);
return BuildDropSignaturePart(result);
}
private string BuildRewardCandidateSignature(int runSeed, int sequenceIndex)
{
_component.ConfigureRunContext(runSeed, sequenceIndex);
IReadOnlyList<TowerCompItemData> candidates = _component.BuildRewardCandidates(8, LevelThemeType.Plain, 3);
int nextRewardOrdinal = 0;
IReadOnlyList<TowerCompItemData> candidates = _component.BuildRewardCandidates(
8,
LevelThemeType.Plain,
3,
runSeed,
sequenceIndex,
ref nextRewardOrdinal);
return string.Join("|", candidates.Select(BuildItemSignaturePart));
}
@ -433,5 +479,185 @@ namespace GeometryTD.Tests.EditMode
return ids;
}
}
private sealed class InsertionOrderDataTable<TRow> : IDataTable<TRow> where TRow : class, IDataRow
{
private readonly List<TRow> _rows = new();
private readonly Dictionary<int, TRow> _rowsById = new();
public InsertionOrderDataTable(params TRow[] rows)
{
if (rows == null)
{
return;
}
for (int i = 0; i < rows.Length; i++)
{
TRow row = rows[i];
if (row == null)
{
continue;
}
_rows.Add(row);
_rowsById[row.Id] = row;
}
}
public string Name => typeof(TRow).Name;
public string FullName => typeof(TRow).FullName;
public Type Type => typeof(TRow);
public int Count => _rows.Count;
public TRow this[int id] => GetDataRow(id);
public TRow MinIdDataRow => _rowsById.Count <= 0 ? null : GetDataRow(GetOrderedIds()[0]);
public TRow MaxIdDataRow => _rowsById.Count <= 0 ? null : GetDataRow(GetOrderedIds()[^1]);
public bool HasDataRow(int id) => _rowsById.ContainsKey(id);
public bool HasDataRow(Predicate<TRow> condition) => GetDataRow(condition) != null;
public TRow GetDataRow(int id) => _rowsById.TryGetValue(id, out TRow row) ? row : null;
public TRow GetDataRow(Predicate<TRow> condition)
{
if (condition == null)
{
return null;
}
for (int i = 0; i < _rows.Count; i++)
{
TRow row = _rows[i];
if (row != null && condition(row))
{
return row;
}
}
return null;
}
public TRow[] GetDataRows(Predicate<TRow> condition)
{
List<TRow> results = new();
GetDataRows(condition, results);
return results.ToArray();
}
public void GetDataRows(Predicate<TRow> condition, List<TRow> results)
{
results?.Clear();
if (condition == null || results == null)
{
return;
}
for (int i = 0; i < _rows.Count; i++)
{
TRow row = _rows[i];
if (row != null && condition(row))
{
results.Add(row);
}
}
}
public TRow[] GetDataRows(Comparison<TRow> comparison)
{
List<TRow> results = new();
GetDataRows(comparison, results);
return results.ToArray();
}
public void GetDataRows(Comparison<TRow> comparison, List<TRow> results)
{
results?.Clear();
if (results == null)
{
return;
}
results.AddRange(_rows);
if (comparison != null)
{
results.Sort(comparison);
}
}
public TRow[] GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison)
{
List<TRow> results = new();
GetDataRows(condition, comparison, results);
return results.ToArray();
}
public void GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison, List<TRow> results)
{
GetDataRows(condition, results);
if (results != null && comparison != null)
{
results.Sort(comparison);
}
}
public TRow[] GetAllDataRows()
{
List<TRow> results = new();
GetAllDataRows(results);
return results.ToArray();
}
public void GetAllDataRows(List<TRow> results)
{
results?.Clear();
if (results == null)
{
return;
}
results.AddRange(_rows);
}
public bool AddDataRow(string dataRowString, object userData) => throw new NotSupportedException();
public bool AddDataRow(byte[] dataRowBytes, int startIndex, int length, object userData) => throw new NotSupportedException();
public bool RemoveDataRow(int id)
{
if (!_rowsById.TryGetValue(id, out TRow row))
{
return false;
}
_rowsById.Remove(id);
return _rows.Remove(row);
}
public void RemoveAllDataRows()
{
_rows.Clear();
_rowsById.Clear();
}
public IEnumerator<TRow> GetEnumerator()
{
for (int i = 0; i < _rows.Count; i++)
{
yield return _rows[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private int[] GetOrderedIds()
{
int[] ids = _rowsById.Keys.ToArray();
Array.Sort(ids);
return ids;
}
}
}
}