Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Shared/AgOpenWeb.RemoteServer/MapBroadcaster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,17 @@ await _ws.BroadcastAsync(WireCodec.EncodeTick(_projector.BuildTick(_sceneVersion
await _ws.BroadcastAsync(WireCodec.EncodeCoverageCells(delta), ct).ConfigureAwait(false);
}
}
else if (_coverageInitSent && !_snapshotInFlight)
{
// The grid is gone (field closed → ClearFieldBounds). Nothing above runs
// without a grid, so say so explicitly or the clients keep drawing the
// closed field's coverage and perimeter until the next field's init.
_coverageInitSent = false;
_coverageReload = false;
_lastCellSize = 0;
_coverageProjector.ResetSent();
await _ws.BroadcastAsync(WireCodec.EncodeCoverageClear(), ct).ConfigureAwait(false);
}
}
catch { /* tolerate transient coverage-layer races */ }

Expand Down
5 changes: 5 additions & 0 deletions Shared/AgOpenWeb.RemoteServer/WireCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,11 @@ public static byte[] EncodeCoverageInit(CoverageInitDto c, bool reset)
return ms.ToArray();
}

/// <summary>No coverage grid any more (field closed): a reset init with an empty grid.
/// The client drops its raster and perimeter (transport.js / app.js onCoverageInit).</summary>
public static byte[] EncodeCoverageClear() =>
EncodeCoverageInit(new CoverageInitDto(0, 0, 0, 0, 0), reset: true);

public static byte[] EncodeCoverageCells(CoverageCellsDto c)
{
var cells = c.Cells;
Expand Down
10 changes: 10 additions & 0 deletions Shared/AgOpenWeb.RemoteServer/wwwroot/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,16 @@ const transport = RemoteTransport.create({
},
onCoverageInit(init) {
const cs = init.cellSize;
// An empty reset grid means the field closed: drop the raster and the perimeter.
if (init.reset && (init.width <= 0 || init.height <= 0)) {
if (cov) {
if (cov.skImg) cov.skImg.delete();
if (cov.surface) cov.surface.delete();
if (cov.covPaint) cov.covPaint.delete();
}
cov = null; covCells = 0; coverageEdges = null;
return;
}
// Grid GROWTH at the same cell size (host bounds-expansion, reset=false): the added ground is
// empty, so the host resends nothing. Keep the coverage we already have, re-anchor it into the
// larger grid, and carry on with incremental deltas — no rescan, no repaint, no flicker. A
Expand Down
6 changes: 6 additions & 0 deletions Shared/AgOpenWeb.ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,12 @@ private void ClearFieldState()
// Clear U-turn state
ClearYouTurnState();

// Clear tram lines and the field's tram systems (saved above). Field open clears them
// too, but between close and the next open the map was still drawing the old lines.
_tramLineService.Clear();
_tramSystemLineRanges.Clear();
ConfigStore.Tram.Systems.Clear();

// Clear flags (without saving — IsFieldOpen is already false) so they don't carry
// over into the next field (#107)
LoadFlagsFromField(null);
Expand Down
19 changes: 19 additions & 0 deletions Tests/AgOpenWeb.ViewModels.Tests/FlagPersistenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,23 @@ public async Task ClosingTheField_ClearsTheBackgroundImage()

Assert.That(vm.State.Field.Imagery, Is.Null, "the web client keeps drawing the picture while the scene carries it");
}

[Test]
public async Task ClosingTheField_ClearsCoverageAndTramLines()
{
var builder = new MainViewModelBuilder();
builder.FieldService.ActiveField.Returns(new Field { Name = "F", DirectoryPath = _dir });
var vm = builder.Build();
vm.IsFieldOpen = true;
var tram = AgOpenWeb.Models.Configuration.ConfigurationStore.Instance.Tram;
tram.Systems.Clear();
tram.Systems.Add(new AgOpenWeb.Models.Tram.TramSystem { Name = "T" });

await vm.CloseFieldAsync();

builder.CoverageMapService.Received().ClearAll();
builder.CoverageMapService.Received().ClearFieldBounds();
builder.TramLineService.Received().Clear();
Assert.That(tram.Systems, Is.Empty, "tram systems are field-scoped; they were saved before the clear");
}
}
3 changes: 2 additions & 1 deletion Tests/AgOpenWeb.ViewModels.Tests/MainViewModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class MainViewModelBuilder
public IModuleCommunicationService ModuleCommunicationService { get; set; } = Substitute.For<IModuleCommunicationService>();
public IConfigurationService ConfigurationService { get; set; } = Substitute.For<IConfigurationService>();
public IFieldService FieldService { get; } = Substitute.For<IFieldService>();
public AgOpenWeb.Services.Interfaces.ITramLineService TramLineService { get; } = Substitute.For<AgOpenWeb.Services.Interfaces.ITramLineService>();
public AgOpenWeb.Services.Pipeline.PipelineIntents Intents { get; } = new();

public MainViewModelBuilder()
Expand Down Expand Up @@ -87,7 +88,7 @@ public MainViewModel Build()
audioService: Substitute.For<IAudioService>(),
elevationLogService: Substitute.For<IElevationLogService>(),
jobService: Substitute.For<IJobService>(),
tramLineService: Substitute.For<AgOpenWeb.Services.Interfaces.ITramLineService>(),
tramLineService: TramLineService,
gpsPipelineService: GpsPipelineService,
intents: Intents,
logger: NullLogger<MainViewModel>.Instance,
Expand Down
Loading