diff --git a/PACKAGE.md b/PACKAGE.md index f84221c..4d37d8d 100644 --- a/PACKAGE.md +++ b/PACKAGE.md @@ -3,7 +3,7 @@ Internal Maven/Gradle package coordinates: ```kotlin -implementation("dev.takesome:sky-simulation:1.4.2") +implementation("dev.takesome:sky-simulation:1.4.3") ``` Install the package into Maven Local: @@ -21,7 +21,7 @@ repositories { } dependencies { - implementation("dev.takesome:sky-simulation:1.4.2-SNAPSHOT") + implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") } ``` diff --git a/README.md b/README.md index 1d453d5..1604943 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,18 @@ # SkySimulation +

+ SkySimulation banner +

+ +

+ CI + Release + Version + Java + Artifact + License +

+ **SkySimulation** is the Take Some() sky and atmosphere package for Java / jMonkeyEngine worlds. We are **Take Some()** — a small engineering team building game-runtime infrastructure: simulation systems, rendering tools, world technology, and reusable engine modules for our own projects. @@ -56,7 +69,7 @@ encapsulated. Public compatibility is preferred over cosmetic directory moves. ## Cloud weather and generated sky materials -Version `1.4.2` adds smooth runtime atmosphere transitions, typed weather-state subscriptions, expanded cloud-weather diagnostics, generated sky material logging, and stronger DDS/BC normal-map inspection for cloud presets. +Version `1.4.3` extracts weather subscription dispatch from the environment runtime, reduces per-event allocation during weather notifications, and keeps cloud/weather runtime state observable for gameplay systems. - `SkyCloudPreset` provides `CLEAR`, `FAIR`, `OVERCAST`, `WISPY`, `CLOUDY`, `RAIN`, `STORM`, and `NIMBUS`. - `SkyControl.setCloudPreset(preset, seconds)` changes weather by fading current layers out, swapping alpha/normal/scale/motion while invisible, and fading target layers in. @@ -89,7 +102,7 @@ repositories { } dependencies { - implementation("dev.takesome:sky-simulation:1.4.2") + implementation("dev.takesome:sky-simulation:1.4.3") } ``` @@ -110,7 +123,7 @@ repositories { } dependencies { - implementation("dev.takesome:sky-simulation:1.4.2-SNAPSHOT") + implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") } ``` @@ -132,7 +145,7 @@ gradlew.bat packageLocal Build release artifacts locally: ```bat -gradlew.bat :SkyLibrary:assemble -PskySimulationVersion=1.4.2 +gradlew.bat :SkyLibrary:assemble -PskySimulationVersion=1.4.3 ``` Artifacts are generated in: @@ -146,8 +159,8 @@ SkyLibrary/build/libs A GitHub release is produced by pushing a version tag: ```bat -git tag -a v1.4.2 -m "SkySimulation v1.4.2" -git push origin v1.4.2 +git tag -a v1.4.3 -m "SkySimulation v1.4.3" +git push origin v1.4.3 ``` The release workflow publishes the Maven package to GitHub Packages and attaches the JAR, sources, Javadoc, POM, and Gradle module metadata to the GitHub Release. diff --git a/RELEASE_NOTES_v1.4.3.md b/RELEASE_NOTES_v1.4.3.md new file mode 100644 index 0000000..935cb14 --- /dev/null +++ b/RELEASE_NOTES_v1.4.3.md @@ -0,0 +1,27 @@ +# SkySimulation v1.4.3 + +SkySimulation 1.4.3 improves the runtime architecture around game-facing weather events and prepares the package for safer growth behind the protected `master` branch. + +## Highlights + +- Extracted weather subscription management from `SkyEnvironmentRuntime` into `SkyWeatherSubscriptionRegistry`. +- Reduced weather-event dispatch overhead by avoiding per-event `ArrayList` snapshots and active-list `contains()` checks. +- Preserved the existing typed weather subscription API, cancellable handles, listener isolation, and current-state replay behavior. +- Kept runtime weather observability for gameplay, UI, AI, fog-of-war, and audio systems. +- Added release documentation for the `1.4.3` package coordinates. + +## Coordinates + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.3") +``` + +Local development snapshot: + +```kotlin +implementation("dev.takesome:sky-simulation:1.4.3-SNAPSHOT") +``` + +## Validation + +- `gradlew.bat :SkyLibrary:test --no-daemon --console=plain --stacktrace` diff --git a/SkyLibrary/release-notes.md b/SkyLibrary/release-notes.md index e95535f..a51ca96 100644 --- a/SkyLibrary/release-notes.md +++ b/SkyLibrary/release-notes.md @@ -1,5 +1,13 @@ # release log for the SkyControl library and related software +## Version 1.4.3 released on 27 June 2026 + +Notable changes: ++ extract weather subscription dispatcher from the environment runtime ++ reduce weather event dispatch allocation and active subscription scans ++ preserve weather listener replay, cancellation, and failure isolation behavior ++ update package coordinates and release documentation for version 1.4.3 + ## Version 1.4.2 released on 27 June 2026 Notable changes: diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java b/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java index 97dd42a..d6e9275 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/Constants.java @@ -106,6 +106,6 @@ private Constants() { * @return branch and revision (not null, not empty) */ public static String versionShort() { - return "master 1.4.2"; + return "master 1.4.3"; } } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java b/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java index 09c18d6..6662a9c 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/SkyMaterialCore.java @@ -186,7 +186,7 @@ public void setCloudsNormalMap(int layerIndex, String assetPath) { normalMap.setWrap(Texture.WrapMode.Repeat); setTexture(parameterName, normalMap); logger.log(Level.FINE, - "cloud normal map applied: layer={0}, parameter={1}, path={2}, image={3}", + "cloud normal map applied: layer={0}, parameter={1}", new Object[]{layerIndex, parameterName, assetPath, normalMap.getImage()}); } @@ -701,7 +701,7 @@ private Texture loadNormalMap(String assetPath) { throw exception; } logger.log(Level.INFO, - "using internal compressed texture reader for cloud normal map: {0}", + "using compressed reader for cloud normal map: {0}", assetPath); result = SkyDdsTextureLoader.loadTexture(assetManager, assetPath); } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java index bd3a36a..959d941 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudPresetLoader.java @@ -125,8 +125,8 @@ private static SkyCloudPresetDefinition parseDefinition( SkyCloudPresetDefinition result = new SkyCloudPresetDefinition( id, description, seconds, layers, metrics); logger.log(Level.FINE, - "parsed sky weather preset: id={0}, seconds={1}, layers={2}, metrics={3}", - new Object[]{id, seconds, layers.size(), metrics}); + "parsed sky weather preset: id={0}, seconds={1}, layers={2}", + new Object[]{id, seconds, layers.size()}); return result; } @@ -154,8 +154,8 @@ private static SkyCloudLayerSpec parseLayer(LuaValue value) { alphaMap, normalMap, opacity, scale, uRate, vRate); } logger.log(Level.FINER, - "parsed cloud layer: alpha={0}, normal={1}, opacity={2}, scale={3}, uRate={4}, vRate={5}", - new Object[]{alphaMap, normalMap, opacity, scale, uRate, vRate}); + "parsed cloud layer: alpha={0}, normal={1}, opacity={2}", + new Object[]{alphaMap, normalMap, opacity}); return result; } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java index 1fc51d2..ed34e81 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/cloud/SkyCloudTransitionRuntime.java @@ -73,7 +73,7 @@ public SkyCloudTransitionRuntime(CloudLayer[] layers) { public void cancel() { if (active && elapsed < duration) { logger.log(Level.FINE, - "cloud weather transition cancelled: target={0}, elapsed={1}, duration={2}", + "cloud transition cancelled: target={0}, elapsed={1}", new Object[]{target == null ? null : target.id(), elapsed, duration}); } @@ -133,8 +133,8 @@ public void transitionTo(SkyCloudPresetDefinition definition, } logger.log(Level.INFO, - "cloud weather transition started: id={0}, seconds={1}, layers={2}", - new Object[]{definition.id(), seconds, definition.layerCount()}); + "cloud transition started: id={0}, seconds={1}", + new Object[]{definition.id(), seconds}); if (seconds == 0f) { applyTargetTextures(); applyTargetOpacities(); @@ -204,9 +204,9 @@ private void applyTargetTextures() { layer.setNormalMap(spec.normalMap()); layer.setMotion(0f, spec.uRate(), 0f, spec.vRate()); logger.log(Level.FINER, - "cloud layer target applied: index={0}, alpha={1}, normal={2}, scale={3}, opacity={4}, uRate={5}, vRate={6}", - new Object[]{layerI, spec.alphaMap(), spec.normalMap(), - spec.scale(), spec.opacity(), spec.uRate(), spec.vRate()}); + "cloud target applied: index={0}, alpha={1}, normal={2}", + new Object[]{layerI, spec.alphaMap(), spec.normalMap()}); + } } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java index 693a50a..1200351 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyDdsTextureLoader.java @@ -121,7 +121,7 @@ private static Image readImage(InputStream stream) throws IOException { MipData mipData = copyMipData( data, width, height, mipLevels, format.blockBytes); logger.log(Level.INFO, - "loaded compressed sky texture: width={0}, height={1}, mips={2}, code={3}, format={4}, blockBytes={5}", + "loaded compressed sky: width={0}, height={1}, mips={2}", new Object[]{width, height, mipData.sizes.length, fourCc, format.imageFormat, format.blockBytes}); Image result = new Image(format.imageFormat, width, height, diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java index ae3eded..35fda1f 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/material/SkyMaterialFactory.java @@ -118,7 +118,7 @@ public static SkyMaterial createTop(AssetManager assetManager, } logger.log(Level.INFO, - "top sky material created: objects={0}, cloudLayers={1}, stars={2}", + "top sky material created: objects={0}, clouds={1}, stars={2}", new Object[]{topObjects, topClouds, starsOption}); return result; } diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java index 4884e16..2a2ab8b 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyEnvironmentRuntime.java @@ -27,11 +27,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; import jme3utilities.Validate; import jme3utilities.sky.cloud.SkyCloudPreset; import jme3utilities.sky.cloud.SkyCloudPresetDefinition; @@ -48,21 +43,15 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * @author Take Some */ final public class SkyEnvironmentRuntime { - /** Message logger for this class. */ - final private static Logger logger - = Logger.getLogger(SkyEnvironmentRuntime.class.getName()); - /** World clock facade. */ final private SkyWorldClock clock; /** Optional weather sink implemented by the visual sky control. */ final private WeatherApplier weatherApplier; - /** Active weather subscriptions. */ - final private List weatherSubscriptions - = new ArrayList(); + /** Weather subscription registry and dispatcher. */ + final private SkyWeatherSubscriptionRegistry weatherSubscriptions + = new SkyWeatherSubscriptionRegistry(); /** Latest lighting output. */ private SkyLightingSnapshot lightingSnapshot = SkyLightingSnapshot.empty(); - /** Runtime-local event sequence counter. */ - private long weatherEventSequence = 0L; /** Current weather state. */ private SkyWeatherState weatherState = SkyWeatherState.fair(); @@ -100,13 +89,7 @@ public float ambientLightLevel() { * Remove all weather subscriptions. */ public void clearWeatherSubscriptions() { - for (SkyWeatherSubscription subscription : weatherSubscriptions) { - subscription.markCancelled(); - } - int removed = weatherSubscriptions.size(); weatherSubscriptions.clear(); - logger.log(Level.FINE, - "sky weather subscriptions cleared: removed={0}", removed); } /** @@ -173,21 +156,8 @@ public float precipitation() { public int removeWeatherListener(SkyWeatherListener listener) { Validate.nonNull(listener, "listener"); - int removed = 0; - Iterator iterator - = weatherSubscriptions.iterator(); - while (iterator.hasNext()) { - SkyWeatherSubscription subscription = iterator.next(); - if (subscription.listener() == listener) { - subscription.markCancelled(); - iterator.remove(); - ++removed; - } - } - logger.log(Level.FINE, - "sky weather listener removed: listener={0}, removed={1}", - new Object[]{listener, removed}); - return removed; + int result = weatherSubscriptions.removeListener(listener); + return result; } /** @@ -362,13 +332,8 @@ public SkyWeatherSubscription subscribeWeather(SkyWeatherFilter filter, Validate.nonNull(filter, "filter"); Validate.nonNull(listener, "listener"); - SkyWeatherSubscription result = new SkyWeatherSubscription( - this, filter, listener); - weatherSubscriptions.add(result); - logger.log(Level.FINE, "sky weather subscription added: {0}", result); - if (notifyCurrent) { - dispatchCurrent(result); - } + SkyWeatherSubscription result = weatherSubscriptions.subscribe( + this, filter, listener, weatherState, notifyCurrent); return result; } @@ -395,13 +360,8 @@ public boolean unsubscribeWeather( return false; } - boolean removed = weatherSubscriptions.remove(subscription); - if (removed) { - subscription.markCancelled(); - logger.log(Level.FINE, - "sky weather subscription removed: {0}", subscription); - } - return removed; + boolean result = weatherSubscriptions.unsubscribe(subscription); + return result; } /** @@ -451,41 +411,6 @@ public float windStrength() { return weatherState.windStrength(); } - /** - * Deliver a current-state replay to a new subscription. - * - * @param subscription subscription to notify - */ - private void dispatchCurrent(SkyWeatherSubscription subscription) { - assert subscription != null; - if (!subscription.matches(weatherState)) { - return; - } - - SkyWeatherEvent event = new SkyWeatherEvent(++weatherEventSequence, - weatherState, weatherState, 0f, SkyWeatherChangeSource.CURRENT); - dispatchSafely(subscription, event); - } - - /** - * Dispatch an event and isolate listener failures. - * - * @param subscription destination subscription - * @param event event payload - */ - private void dispatchSafely(SkyWeatherSubscription subscription, - SkyWeatherEvent event) { - assert subscription != null; - assert event != null; - try { - subscription.dispatch(event); - } catch (RuntimeException exception) { - logger.log(Level.WARNING, - "sky weather listener failed: subscription=" - + subscription + ", event=" + event, exception); - } - } - /** * Publish a weather-state change. * @@ -497,24 +422,7 @@ private void dispatchSafely(SkyWeatherSubscription subscription, private void publishWeatherChange(SkyWeatherState previous, SkyWeatherState current, float seconds, SkyWeatherChangeSource source) { - assert previous != null; - assert current != null; - assert seconds >= 0f : seconds; - assert source != null; - - SkyWeatherEvent event = new SkyWeatherEvent(++weatherEventSequence, - previous, current, seconds, source); - logger.log(Level.INFO, "sky weather changed: {0}", event); - - SkyWeatherState eventState = event.currentWeather(); - List snapshot - = new ArrayList(weatherSubscriptions); - for (SkyWeatherSubscription subscription : snapshot) { - if (weatherSubscriptions.contains(subscription) - && subscription.matches(eventState)) { - dispatchSafely(subscription, event); - } - } + weatherSubscriptions.publish(previous, current, seconds, source); } /** diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java index 4c4d87f..f6af3f9 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherEvent.java @@ -144,6 +144,11 @@ public float transitionSeconds() { return transitionSeconds; } + /** + * Describe this weather event. + * + * @return description string + */ @Override public String toString() { return "SkyWeatherEvent[sequence=" + sequence diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java index 4760a47..d4f3a91 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherMetrics.java @@ -124,6 +124,11 @@ public float windStrength() { return windStrength; } + /** + * Describe this weather metrics. + * + * @return description string + */ @Override public String toString() { return "SkyWeatherMetrics[cloudiness=" + cloudiness diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java index 789b570..7a5f490 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherState.java @@ -271,6 +271,11 @@ public float windStrength() { return metrics.windStrength(); } + /** + * Describe this weather state. + * + * @return description string + */ @Override public String toString() { return "SkyWeatherState[id=" + id diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java index e973126..4cad8eb 100644 --- a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscription.java @@ -79,33 +79,57 @@ public boolean isActive() { return active; } - /** Dispatch an event to the listener. */ + /** + * Dispatch an event to the listener. + * + * @param event event payload + */ void dispatch(SkyWeatherEvent event) { assert event != null; listener.onWeatherChanged(event); } - /** Return the listener. */ + /** + * Return the listener. + * + * @return listener callback + */ SkyWeatherListener listener() { return listener; } - /** Mark the subscription as cancelled. */ + /** + * Mark the subscription as cancelled. + */ void markCancelled() { active = false; } - /** Test whether a state matches this subscription. */ + /** + * Test whether a state matches this subscription. + * + * @param state candidate weather state + * @return true if the state matches + */ boolean matches(SkyWeatherState state) { assert state != null; return active && filter.matches(state); } - /** Return the owning runtime. */ + /** + * Return the owning runtime. + * + * @return owning runtime + */ SkyEnvironmentRuntime owner() { return owner; } + /** + * Describe this subscription. + * + * @return description string + */ @Override public String toString() { return "SkyWeatherSubscription[active=" + active diff --git a/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java new file mode 100644 index 0000000..6ceab8b --- /dev/null +++ b/SkyLibrary/src/main/java/jme3utilities/sky/runtime/SkyWeatherSubscriptionRegistry.java @@ -0,0 +1,207 @@ +/* + Copyright (c) 2026, Take Some + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.sky.runtime; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Registry and dispatcher for game-facing weather subscriptions. + * + * @author Take Some + */ +final class SkyWeatherSubscriptionRegistry { + /** Message logger for this class. */ + final private static Logger logger + = Logger.getLogger(SkyWeatherSubscriptionRegistry.class.getName()); + + /** Active weather subscriptions. */ + final private CopyOnWriteArrayList subscriptions + = new CopyOnWriteArrayList(); + + /** Runtime-local event sequence counter. */ + private long eventSequence = 0L; + + /** + * Remove all subscriptions. + * + * @return number of removed subscriptions + */ + int clear() { + for (SkyWeatherSubscription subscription : subscriptions) { + subscription.markCancelled(); + } + int removed = subscriptions.size(); + subscriptions.clear(); + logger.log(Level.FINE, + "sky weather subscriptions cleared: removed={0}", removed); + return removed; + } + + /** + * Publish a weather-state change. + * + * @param previous previous state + * @param current current state + * @param seconds requested transition duration + * @param source source category + */ + void publish(SkyWeatherState previous, SkyWeatherState current, + float seconds, SkyWeatherChangeSource source) { + assert previous != null; + assert current != null; + assert seconds >= 0f : seconds; + assert source != null; + + SkyWeatherEvent event = new SkyWeatherEvent(++eventSequence, + previous, current, seconds, source); + logger.log(Level.INFO, "sky weather changed: {0}", event); + + SkyWeatherState eventState = event.currentWeather(); + for (SkyWeatherSubscription subscription : subscriptions) { + if (subscription.matches(eventState)) { + dispatchSafely(subscription, event); + } + } + } + + /** + * Remove all subscriptions owned by the specified listener. + * + * @param listener listener to remove + * @return number of removed subscriptions + */ + int removeListener(SkyWeatherListener listener) { + assert listener != null; + + int removed = 0; + for (SkyWeatherSubscription subscription : subscriptions) { + if (subscription.listener() == listener + && subscriptions.remove(subscription)) { + subscription.markCancelled(); + ++removed; + } + } + logger.log(Level.FINE, + "sky weather listener removed: listener={0}, removed={1}", + new Object[]{listener, removed}); + return removed; + } + + /** + * Return the number of active subscriptions. + * + * @return subscription count + */ + int size() { + return subscriptions.size(); + } + + /** + * Add a subscription. + * + * @param owner owning runtime + * @param filter subscription filter + * @param listener callback + * @param current current weather state + * @param notifyCurrent true to replay the current state if it matches + * @return new subscription + */ + SkyWeatherSubscription subscribe(SkyEnvironmentRuntime owner, + SkyWeatherFilter filter, SkyWeatherListener listener, + SkyWeatherState current, boolean notifyCurrent) { + assert owner != null; + assert filter != null; + assert listener != null; + assert current != null; + + SkyWeatherSubscription result = new SkyWeatherSubscription( + owner, filter, listener); + subscriptions.add(result); + logger.log(Level.FINE, "sky weather subscription added: {0}", result); + if (notifyCurrent) { + dispatchCurrent(result, current); + } + return result; + } + + /** + * Remove a subscription. + * + * @param subscription subscription to remove + * @return true if removed + */ + boolean unsubscribe(SkyWeatherSubscription subscription) { + assert subscription != null; + + boolean removed = subscriptions.remove(subscription); + if (removed) { + subscription.markCancelled(); + logger.log(Level.FINE, + "sky weather subscription removed: {0}", subscription); + } + return removed; + } + + /** + * Deliver a current-state replay to a new subscription. + * + * @param subscription subscription to notify + * @param current current weather state + */ + private void dispatchCurrent(SkyWeatherSubscription subscription, + SkyWeatherState current) { + assert subscription != null; + assert current != null; + if (!subscription.matches(current)) { + return; + } + + SkyWeatherEvent event = new SkyWeatherEvent(++eventSequence, + current, current, 0f, SkyWeatherChangeSource.CURRENT); + dispatchSafely(subscription, event); + } + + /** + * Dispatch an event and isolate common listener failures. + * + * @param subscription destination subscription + * @param event event payload + */ + private void dispatchSafely(SkyWeatherSubscription subscription, + SkyWeatherEvent event) { + assert subscription != null; + assert event != null; + try { + subscription.dispatch(event); + } catch (IllegalArgumentException | IllegalStateException exception) { + logger.log(Level.WARNING, + "sky weather listener failed: subscription=" + + subscription + ", event=" + event, exception); + } + } +} diff --git a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java index 35005d0..062a336 100644 --- a/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java +++ b/SkyLibrary/src/test/java/jme3utilities/sky/test/TestSkyEnvironmentRuntime.java @@ -129,7 +129,7 @@ public void testWeatherSubscriptions() { final String[] currentId = {null}; final float[] transitionSeconds = {-1f}; - SkyWeatherSubscription all = runtime.subscribeWeather( + final SkyWeatherSubscription all = runtime.subscribeWeather( new SkyWeatherListener() { @Override public void onWeatherChanged(SkyWeatherEvent event) { diff --git a/build.gradle b/build.gradle index b13f021..ab44f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { ext { jmeTarget = '' // distinguish non-JME libraries built for specific JME releases - skySimulationBaseVersion = '1.4.2' + skySimulationBaseVersion = '1.4.3' skySnapshot = hasProperty('releaseBuild') ? '' : '-SNAPSHOT' skySimulationGroup = 'dev.takesome' skySimulationArtifact = 'sky-simulation' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index f33a349..45ab722 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -243,11 +243,11 @@ - + - + @@ -259,7 +259,7 @@ - + diff --git a/docs/sky-simulation-banner.svg b/docs/sky-simulation-banner.svg new file mode 100644 index 0000000..e0264c4 --- /dev/null +++ b/docs/sky-simulation-banner.svg @@ -0,0 +1,37 @@ + + SkySimulation banner + A stylized sky runtime banner with sun, moon, clouds, stars, and atmosphere layers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SkySimulation + Take Some() sky, weather, atmosphere and world-runtime infrastructure + dev.takesome:sky-simulation:1.4.3 +