-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathEntityRenderOrder.java
More file actions
135 lines (122 loc) · 4.88 KB
/
EntityRenderOrder.java
File metadata and controls
135 lines (122 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.demcha.compose.engine.render;
import com.demcha.compose.engine.components.core.Entity;
import com.demcha.compose.engine.components.layout.coordinator.ComputedPosition;
import com.demcha.compose.engine.components.layout.coordinator.RenderingPosition;
import com.demcha.compose.engine.components.style.Margin;
import com.demcha.compose.engine.core.EntityManager;
import lombok.experimental.UtilityClass;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* Stable renderer-neutral ordering for already-laid-out entities.
*
* <p>
* This helper belongs to the rendering layer rather than pagination because it
* only answers "in what order should resolved entities be handed to a
* renderer?"
* It does not decide page-breaking or parent/child pagination rules.
* </p>
*
* <p>
* The ordering contract is:
* </p>
* <ol>
* <li>higher {@link RenderingPosition#y()} first (top to bottom in engine
* coordinates)</li>
* <li>lower {@link RenderingPosition#x()} first for stable left-to-right
* ties</li>
* <li>original layer order when provided</li>
* <li>{@link UUID} as the final deterministic fallback</li>
* </ol>
*/
@UtilityClass
public class EntityRenderOrder {
/**
* Resolves entities from the manager and returns them sorted by rendering
* position.
*
* <p>
* Duplicate or missing entity IDs are silently skipped. The returned map
* preserves the computed render order.
* </p>
*
* @param entityManager entity registry to resolve entities from
* @param entityUuids ordered list of entity IDs to include
* @return entities sorted by the {@linkplain #renderOrderComparator rendering
* order}
*/
public static LinkedHashMap<UUID, Entity> sortByRenderingPosition(EntityManager entityManager,
List<UUID> entityUuids) {
Objects.requireNonNull(entityManager, "entityManager must not be null");
Objects.requireNonNull(entityUuids, "entityUuids must not be null");
LinkedHashMap<UUID, Entity> entities = new LinkedHashMap<>();
HashSet<UUID> seen = new HashSet<>();
for (int i = 0; i < entityUuids.size(); i++) {
UUID entityId = entityUuids.get(i);
if (!seen.add(entityId)) {
continue;
}
Entity entity = entityManager.getEntity(entityId).orElse(null);
if (entity == null) {
continue;
}
entities.put(entityId, entity);
}
if (entities.size() <= 1) {
return entities;
}
List<RenderEntry> resolvedEntries = new java.util.ArrayList<>(entities.size());
int originalIndex = 0;
for (var entry : entities.entrySet()) {
resolvedEntries.add(resolveRenderEntry(entry.getKey(), entry.getValue(), originalIndex++));
}
resolvedEntries.sort(renderOrderComparator());
LinkedHashMap<UUID, Entity> ordered = new LinkedHashMap<>(resolvedEntries.size());
for (RenderEntry entry : resolvedEntries) {
ordered.put(entry.id(), entry.entity());
}
return ordered;
}
/**
* Builds the render-order comparator using Y descending, X ascending, original
* layer index, and UUID string as the final deterministic fallback.
*/
private static Comparator<RenderEntry> renderOrderComparator() {
return Comparator
.comparingDouble(RenderEntry::y)
.reversed()
.thenComparingDouble(RenderEntry::x)
.thenComparingInt(RenderEntry::originalIndex)
.thenComparing(entry -> entry.id().toString());
}
/**
* Resolves the lightweight sort payload for one entity.
*
* <p>
* This precomputation keeps component lookups and margin fallback handling out
* of the comparator hot path.
* </p>
*
* @param entityId entity identifier
* @param entity resolved entity instance
* @param originalIndex original order inside the incoming layer
* @return immutable render-order entry
*/
private static RenderEntry resolveRenderEntry(UUID entityId, Entity entity, int originalIndex) {
ComputedPosition computedPosition = entity.getComponent(ComputedPosition.class)
.orElseThrow(() -> new IllegalStateException("Entity " + entity + " has no RenderingPosition"));
Margin margin = entity.getComponent(Margin.class).orElse(Margin.zero());
double x = computedPosition.x() + margin.left();
double y = computedPosition.y() + margin.bottom();
return new RenderEntry(entityId, entity, x, y, originalIndex);
}
/**
* Immutable sort payload used during one render-order computation.
*/
private record RenderEntry(UUID id, Entity entity, double x, double y, int originalIndex) {
}
}