Убрать аллокации из игрового hot path
This commit is contained in:
@@ -18,12 +18,10 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A dependency-free 2D puzzle-platformer renderer and deterministic physics loop.
|
||||
@@ -175,6 +173,8 @@ public final class GameView extends View implements Runnable {
|
||||
private final Map<String, Boolean> toggleSignals = new HashMap<>();
|
||||
private final Map<String, Float> timedSignals = new HashMap<>();
|
||||
private final Map<String, Boolean> previousSwitchContact = new HashMap<>();
|
||||
private final AccessibilityStateCache accessibilityStateCache =
|
||||
new AccessibilityStateCache();
|
||||
|
||||
private Screen screen = Screen.LEVELS;
|
||||
private LevelDefinition level;
|
||||
@@ -199,7 +199,16 @@ public final class GameView extends View implements Runnable {
|
||||
private GameLanguage language;
|
||||
private GameStrings strings;
|
||||
private AccessibilitySettings accessibilitySettings;
|
||||
private String lastAccessibilityStateKey = "";
|
||||
private String cachedLevelHeader = "";
|
||||
private String cachedLevelHint = "";
|
||||
private float cachedLevelHintSize = 25f;
|
||||
private int cachedHudLevelNumber = -1;
|
||||
private GameLanguage cachedHudLanguage;
|
||||
private float cachedHudTextScale = Float.NaN;
|
||||
private String cachedHudStats = "";
|
||||
private long cachedHudStatsSecond = Long.MIN_VALUE;
|
||||
private int cachedHudStatsDeaths = Integer.MIN_VALUE;
|
||||
private GameLanguage cachedHudStatsLanguage;
|
||||
|
||||
public GameView(Context context) {
|
||||
super(context);
|
||||
@@ -289,6 +298,8 @@ public final class GameView extends View implements Runnable {
|
||||
timedSignals.clear();
|
||||
previousSwitchContact.clear();
|
||||
completionResult = null;
|
||||
configureAccessibilitySignals();
|
||||
invalidateHudTextCache();
|
||||
for (LevelDefinition.Platform item : level.platforms) platformStates.add(new PlatformState(item));
|
||||
spawnPlayer();
|
||||
lastFrameNanos = System.nanoTime();
|
||||
@@ -978,20 +989,19 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
|
||||
private void drawHud(Canvas canvas) {
|
||||
ensureHudTextCache();
|
||||
paint.setColor(Color.argb(178, 15, 20, 34));
|
||||
canvas.drawRoundRect(new RectF(28, 26, 940, 138), 24, 24, paint);
|
||||
canvas.save();
|
||||
canvas.clipRect(40, 30, 925, 135);
|
||||
drawText(canvas, String.format(Locale.US, "%02d %s", level.number,
|
||||
strings.levelTitle(level.number)), 56, 72, 34,
|
||||
Color.rgb(248, 220, 151), false);
|
||||
drawFittedText(canvas, strings.levelHint(level.number), 56, 116, 25, 16,
|
||||
850, Color.rgb(231, 231, 235), false);
|
||||
drawText(canvas, cachedLevelHeader, 56, 72, 34, Color.rgb(248, 220, 151),
|
||||
false);
|
||||
drawText(canvas, cachedLevelHint, 56, 116, cachedLevelHintSize,
|
||||
Color.rgb(231, 231, 235), false);
|
||||
canvas.restore();
|
||||
canvas.save();
|
||||
canvas.clipRect(990, 28, 1590, 130);
|
||||
drawText(canvas, strings.hudStats(deaths, (long) (levelElapsed * 1000)),
|
||||
1010, 82, 31, Color.WHITE, false);
|
||||
drawText(canvas, hudStatsText(), 1010, 82, 31, Color.WHITE, false);
|
||||
canvas.restore();
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.BLACK : Color.argb(178, 15, 20, 34));
|
||||
@@ -1743,6 +1753,7 @@ public final class GameView extends View implements Runnable {
|
||||
language = updated == null ? GameLanguage.EN : updated;
|
||||
strings = GameStrings.forLanguage(language);
|
||||
languageStore.save(language);
|
||||
invalidateHudTextCache();
|
||||
refreshAccessibilityDescription(false);
|
||||
invalidate();
|
||||
}
|
||||
@@ -1824,20 +1835,76 @@ public final class GameView extends View implements Runnable {
|
||||
private void updateAccessibilitySettings(AccessibilitySettings updated) {
|
||||
accessibilitySettings = updated;
|
||||
accessibilitySettingsStore.save(updated);
|
||||
invalidateHudTextCache();
|
||||
refreshAccessibilityDescription(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private String criticalStatusText() {
|
||||
if (level == null) return AccessibilityStatus.describe(strings, 0, 0, 0, 0, true);
|
||||
Set<String> signalIds = new HashSet<>();
|
||||
private void invalidateHudTextCache() {
|
||||
cachedHudLevelNumber = -1;
|
||||
cachedHudLanguage = null;
|
||||
cachedHudTextScale = Float.NaN;
|
||||
cachedHudStatsSecond = Long.MIN_VALUE;
|
||||
cachedHudStatsDeaths = Integer.MIN_VALUE;
|
||||
cachedHudStatsLanguage = null;
|
||||
}
|
||||
|
||||
private void ensureHudTextCache() {
|
||||
if (level == null) return;
|
||||
float textScale = accessibilitySettings.textScale();
|
||||
if (cachedHudLevelNumber == level.number
|
||||
&& cachedHudLanguage == language
|
||||
&& Float.compare(cachedHudTextScale, textScale) == 0) {
|
||||
return;
|
||||
}
|
||||
cachedHudLevelNumber = level.number;
|
||||
cachedHudLanguage = language;
|
||||
cachedHudTextScale = textScale;
|
||||
cachedLevelHeader = String.format(Locale.US, "%02d %s", level.number,
|
||||
strings.levelTitle(level.number));
|
||||
cachedLevelHint = strings.levelHint(level.number);
|
||||
cachedLevelHintSize = fittedTextSize(cachedLevelHint, 25f, 16f, 850f, false);
|
||||
}
|
||||
|
||||
private String hudStatsText() {
|
||||
long displayedSecond = (long) (levelElapsed * 1000f) / 1000L;
|
||||
if (cachedHudStatsSecond != displayedSecond
|
||||
|| cachedHudStatsDeaths != deaths
|
||||
|| cachedHudStatsLanguage != language) {
|
||||
cachedHudStatsSecond = displayedSecond;
|
||||
cachedHudStatsDeaths = deaths;
|
||||
cachedHudStatsLanguage = language;
|
||||
cachedHudStats = strings.hudStats(deaths, displayedSecond * 1000L);
|
||||
}
|
||||
return cachedHudStats;
|
||||
}
|
||||
|
||||
private void configureAccessibilitySignals() {
|
||||
ArrayList<String> signalIds = new ArrayList<>();
|
||||
if (level != null) {
|
||||
for (LevelDefinition.Switch item : level.switches) signalIds.add(item.signalId);
|
||||
}
|
||||
accessibilityStateCache.configureSignals(signalIds);
|
||||
}
|
||||
|
||||
private boolean updateCriticalStatusCache() {
|
||||
int activeSignals = 0;
|
||||
for (LevelDefinition.Switch item : level.switches) signalIds.add(item.signalId);
|
||||
for (String signalId : signalIds) if (signalActive(signalId)) activeSignals++;
|
||||
if (level != null) {
|
||||
for (String signalId : accessibilityStateCache.signalIds()) {
|
||||
if (signalActive(signalId)) activeSignals++;
|
||||
}
|
||||
}
|
||||
int openDoors = 0;
|
||||
for (LevelDefinition.Door door : level.doors) if (doorOpen(door)) openDoors++;
|
||||
return AccessibilityStatus.describe(strings, activeSignals, signalIds.size(), openDoors,
|
||||
level.doors.size(), player.alive);
|
||||
if (level != null) {
|
||||
for (LevelDefinition.Door door : level.doors) if (doorOpen(door)) openDoors++;
|
||||
}
|
||||
return accessibilityStateCache.update(strings, activeSignals, openDoors,
|
||||
level == null ? 0 : level.doors.size(), level == null || player.alive);
|
||||
}
|
||||
|
||||
private String criticalStatusText() {
|
||||
if (accessibilityStateCache.statusText() == null) updateCriticalStatusCache();
|
||||
return accessibilityStateCache.statusText();
|
||||
}
|
||||
|
||||
private String currentAccessibilityDescription() {
|
||||
@@ -1859,23 +1926,16 @@ public final class GameView extends View implements Runnable {
|
||||
userPaused);
|
||||
}
|
||||
|
||||
private String accessibilityStateKey() {
|
||||
return screen + "|" + userPaused + "|" + deaths + "|" + criticalStatusText()
|
||||
+ "|" + controlSettings.controlSizeLabel()
|
||||
+ "|" + controlSettings.controlOpacityLabel()
|
||||
+ "|" + controlSettings.hapticsEnabled()
|
||||
+ "|" + accessibilitySettings.textSizeLabel()
|
||||
+ "|" + accessibilitySettings.highContrastEnabled()
|
||||
+ "|" + language;
|
||||
}
|
||||
|
||||
private void refreshAccessibilityDescriptionIfStateChanged() {
|
||||
String stateKey = accessibilityStateKey();
|
||||
if (!stateKey.equals(lastAccessibilityStateKey)) refreshAccessibilityDescription(false);
|
||||
if (updateCriticalStatusCache()) applyAccessibilityDescription(false);
|
||||
}
|
||||
|
||||
private void refreshAccessibilityDescription(boolean announce) {
|
||||
lastAccessibilityStateKey = accessibilityStateKey();
|
||||
updateCriticalStatusCache();
|
||||
applyAccessibilityDescription(announce);
|
||||
}
|
||||
|
||||
private void applyAccessibilityDescription(boolean announce) {
|
||||
setContentDescription(currentAccessibilityDescription());
|
||||
if (announce && isAttachedToWindow()) {
|
||||
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
||||
|
||||
Reference in New Issue
Block a user