Repository navigation
Systems 8b: the current project is state the window follows #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c124c23
The current project is state the window follows
Pelotrio 05c8ae7
SYSTEMS.md: the Changes page's read noticing put-back values stays, d…
Pelotrio 3a12518
Close project follower subscription and disposal gaps
Pelotrio 6bc0856
Wait for published connection state in discovery tests
Pelotrio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
...ion/src/main/java/com/github/minecraft_ta/totalDebugCompanion/project/CurrentProject.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.github.minecraft_ta.totalDebugCompanion.project; | ||
|
|
||
| import com.github.minecraft_ta.totalDebugCompanion.util.Signal; | ||
|
|
||
| import javax.swing.SwingUtilities; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * The project Companion shows now (docs/SYSTEMS.md, section 1). The window follows a signal of whichever project is | ||
| * current through {@link #follows}, which moves to the next project on a switch and tells nothing of one no longer | ||
| * current, so no follower checks which project a change came from. | ||
| */ | ||
| public final class CurrentProject { | ||
| private final Signal changed = new Signal(); | ||
| private volatile ProjectScope scope; | ||
|
|
||
| /** The current project, or null without one. */ | ||
| public ProjectScope scope() { | ||
| return this.scope; | ||
| } | ||
|
|
||
| /** Fires after another project became current, or none, on the thread that switched. */ | ||
| public Signal changed() { | ||
| return this.changed; | ||
| } | ||
|
|
||
| /** Makes {@code scope} the current project, or none when null, and tells the followers where it changed. */ | ||
| public void set(ProjectScope scope) { | ||
| if (this.scope == scope) return; | ||
| this.scope = scope; | ||
| this.changed.fire(); | ||
| } | ||
|
|
||
| /** | ||
| * Runs {@code told} on the Swing thread whenever the signal {@code signal} names of the current project fires, while | ||
| * that project is still current; follows the next project's after a switch. Returns what stops it. | ||
| */ | ||
| public Runnable follows(Function<ProjectScope, Signal> signal, Runnable told) { | ||
| Objects.requireNonNull(signal, "signal"); | ||
| Objects.requireNonNull(told, "told"); | ||
| Object lock = new Object(); | ||
| AtomicBoolean stopped = new AtomicBoolean(); | ||
| Runnable[] fromScope = {() -> { }}; | ||
| Runnable move = () -> { | ||
| synchronized (lock) { | ||
| if (stopped.get()) return; | ||
| fromScope[0].run(); | ||
| ProjectScope now = this.scope; | ||
| fromScope[0] = now == null ? () -> { } : signal.apply(now).subscribe(() -> SwingUtilities.invokeLater(() -> { | ||
| if (!stopped.get() && this.scope == now) told.run(); | ||
| })); | ||
| } | ||
| }; | ||
| Runnable fromProject = this.changed.subscribe(move); | ||
| move.run(); | ||
| return () -> { | ||
| stopped.set(true); | ||
| fromProject.run(); | ||
| synchronized (lock) { | ||
| fromScope[0].run(); | ||
| fromScope[0] = () -> { }; | ||
| } | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
...src/test/java/com/github/minecraft_ta/totalDebugCompanion/project/CurrentProjectTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| package com.github.minecraft_ta.totalDebugCompanion.project; | ||
|
|
||
| import com.github.minecraft_ta.totalDebugCompanion.session.CompanionProfile; | ||
| import com.github.minecraft_ta.totalDebugCompanion.storage.ChangeRecord; | ||
| import com.github.minecraft_ta.totalDebugCompanion.storage.InstanceState; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.io.TempDir; | ||
|
|
||
| import javax.swing.SwingUtilities; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.concurrent.CountDownLatch; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| /** The project the window shows, whose changes reach the window only while it is the current one. */ | ||
| class CurrentProjectTest { | ||
| @TempDir Path directory; | ||
|
|
||
| @Test | ||
| void aFollowerHearsOnlyTheCurrentProjectAndMovesWithASwitch() throws Exception { | ||
| try (ProjectScope first = scope("first"); ProjectScope second = scope("second")) { | ||
| CurrentProject current = new CurrentProject(); | ||
| current.set(first); | ||
| AtomicInteger told = new AtomicInteger(); | ||
| Runnable stop = current.follows(scope -> scope.changes().changed(), told::incrementAndGet); | ||
|
|
||
| fire(first); | ||
| assertEquals(1, told.get(), "the current project's change is told"); | ||
|
|
||
| current.set(second); | ||
| fire(first); | ||
| assertEquals(1, told.get(), "a project no longer current tells nothing"); | ||
| fire(second); | ||
| assertEquals(2, told.get(), "the follower moved to the project now current"); | ||
|
|
||
| // A change the previous project told just before the switch arrives after it. | ||
| current.set(first); | ||
| SwingUtilities.invokeAndWait(() -> { | ||
| first.changes().changed().fire(); | ||
| current.set(second); | ||
| }); | ||
| SwingUtilities.invokeAndWait(() -> { }); | ||
| assertEquals(2, told.get(), "a change of the project before, queued before the switch, is not told"); | ||
|
|
||
| SwingUtilities.invokeAndWait(() -> { | ||
| second.changes().changed().fire(); | ||
| stop.run(); | ||
| }); | ||
| SwingUtilities.invokeAndWait(() -> { }); | ||
| assertEquals(2, told.get(), "a change queued before stopping is not delivered afterward"); | ||
| fire(second); | ||
| assertEquals(2, told.get(), "a follower that stopped hears nothing"); | ||
| first.retire(); | ||
| second.retire(); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void aSwitchDuringInitialBindingIsNotMissed() throws Exception { | ||
| try (ProjectScope first = scope("first"); ProjectScope second = scope("second")) { | ||
| CurrentProject current = new CurrentProject(); | ||
| current.set(first); | ||
| CountDownLatch binding = new CountDownLatch(1); | ||
| CountDownLatch switched = new CountDownLatch(1); | ||
| Runnable stopObservingSwitch = current.changed().subscribe(switched::countDown); | ||
| Thread switching = Thread.ofPlatform().daemon().name("Test project switch").start(() -> { | ||
| try { | ||
| if (binding.await(5, TimeUnit.SECONDS)) current.set(second); | ||
| } catch (InterruptedException interrupted) { | ||
| Thread.currentThread().interrupt(); | ||
| } | ||
| }); | ||
| Runnable stop = () -> { }; | ||
| try { | ||
| AtomicInteger told = new AtomicInteger(); | ||
| stop = current.follows(selected -> { | ||
| if (selected == first) { | ||
| binding.countDown(); | ||
| try { | ||
| assertTrue(switched.await(5, TimeUnit.SECONDS)); | ||
| } catch (InterruptedException interrupted) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new AssertionError(interrupted); | ||
| } | ||
| } | ||
| return selected.changes().changed(); | ||
| }, told::incrementAndGet); | ||
| switching.join(5_000); | ||
| assertFalse(switching.isAlive()); | ||
| fire(second); | ||
| assertEquals(1, told.get(), "the follower binds to the project selected during its initial binding"); | ||
| fire(first); | ||
| assertEquals(1, told.get(), "the previous project has no remaining delivery"); | ||
| } finally { | ||
| stop.run(); | ||
| stopObservingSwitch.run(); | ||
| first.retire(); | ||
| second.retire(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private ProjectScope scope(String name) throws Exception { | ||
| Path game = Files.createDirectories(this.directory.resolve(name)); | ||
| return new ProjectScope(new Object(), new CompanionProfile(name, game, game), InstanceState.inMemory(), ChangeRecord.inMemory()); | ||
| } | ||
|
|
||
| private static void fire(ProjectScope scope) throws Exception { | ||
| scope.changes().changed().fire(); | ||
| SwingUtilities.invokeAndWait(() -> { }); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a project signal races with
MainWindow.dispose(), its EDT lambda may already be queued even though the returned stop action removes the subscriptions; this check only verifies that the project is still current. If the scope remains current,catalogChangedorchangesRecordedtherefore runs after disposal and can repopulate the disposedFileTreeViewor attach fresh listeners. Track the subscription's stopped state or generation here and reject queued deliveries after it is stopped.AGENTS.md reference: AGENTS.md:L19-L19
Useful? React with 👍 / 👎.