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** 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 @@
+