• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedDynamics / 22182611236

19 Feb 2026 12:53PM UTC coverage: 52.363% (-0.9%) from 53.26%
22182611236

push

github

rubensworks
Merge remote-tracking branch 'origin/master-1.21-lts' into master-1.21

2916 of 9054 branches covered (32.21%)

Branch coverage included in aggregate %.

17656 of 30233 relevant lines covered (58.4%)

3.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.68
/src/main/java/org/cyclops/integrateddynamics/core/network/diagnostics/NetworkDiagnostics.java
1
package org.cyclops.integrateddynamics.core.network.diagnostics;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.collect.Maps;
5
import com.google.common.collect.Sets;
6
import net.minecraft.ChatFormatting;
7
import net.minecraft.network.chat.Component;
8
import net.minecraft.server.level.ServerPlayer;
9
import net.neoforged.neoforge.server.ServerLifecycleHooks;
10
import org.cyclops.integrateddynamics.IntegratedDynamics;
11
import org.cyclops.integrateddynamics.api.network.*;
12
import org.cyclops.integrateddynamics.api.part.PartPos;
13
import org.cyclops.integrateddynamics.core.persist.world.NetworkWorldStorage;
14
import org.cyclops.integrateddynamics.network.packet.NetworkDiagnosticsNetworkPacket;
15

16
import java.util.*;
17

18
/**
19
 * @author rubensworks
20
 */
21
public class NetworkDiagnostics {
22

23
    private static final NetworkDiagnostics _INSTANCE = new NetworkDiagnostics();
5✔
24

25
    private final List<UUID> players = Lists.newArrayList();
3✔
26
    private final Map<UUID, MeasurementSession> activeMeasurements = Maps.newHashMap();
3✔
27
    private final Set<UUID> playerMeasurements = Sets.newHashSet(); // Track which measurements are player-based
3✔
28
    private final Map<UUID, UUID> playerToMeasurement = Maps.newHashMap(); // Map player UUID to measurement UUID
3✔
29

30
    private NetworkDiagnostics() {
2✔
31

32
    }
1✔
33

34
    public static NetworkDiagnostics getInstance() {
35
        return _INSTANCE;
2✔
36
    }
37

38
    protected ServerPlayer getPlayer(UUID uuid) {
39
        return ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(uuid);
×
40
    }
41

42
    public synchronized void registerPlayer(ServerPlayer player) {
43
        if (!players.contains(player.getUUID())) {
×
44
            players.add(player.getUUID());
×
45
            for (INetwork network : NetworkWorldStorage.Access.getInstance(IntegratedDynamics._instance).get().getNetworks()) {
×
46
                sendNetworkUpdateToPlayer(player, network);
×
47
            }
×
48

49
        }
50
    }
×
51

52
    public synchronized void unRegisterPlayer(ServerPlayer player) {
53
        players.remove(player.getUUID());
×
54
    }
×
55

56
    public void sendNetworkUpdateToPlayer(ServerPlayer player, INetwork network) {
57
        List<RawPartData> rawParts = Lists.newArrayList();
×
58
        for (INetworkElement networkElement : network.getElements()) {
×
59
            if (network.isValid(networkElement) && networkElement instanceof IPartNetworkElement) {
×
60
                IPartNetworkElement partNetworkElement = (IPartNetworkElement) networkElement;
×
61
                PartPos pos = partNetworkElement.getTarget().getCenter();
×
62
                long lastSecondDurationNs = network.getLastSecondDuration(networkElement);
×
63
                rawParts.add(new RawPartData(pos.getPos().getLevelKey(),
×
64
                        pos.getPos().getBlockPos(), pos.getSide(),
×
65
                        partNetworkElement.getPart().getTranslationKey(),
×
66
                        lastSecondDurationNs));
67
            } else {
68
                // If needed, we can send the other part types later on as well
69
            }
70
        }
×
71

72
        List<RawObserverData> rawObservers = Lists.newArrayList();
×
73
        for (IFullNetworkListener fullNetworkListener : network.getFullNetworkListeners()) {
×
74
            if (fullNetworkListener instanceof IPositionedAddonsNetworkIngredients) {
×
75
                IPositionedAddonsNetworkIngredients<?, ?> networkIngredients = (IPositionedAddonsNetworkIngredients<?, ?>) fullNetworkListener;
×
76
                Map<PartPos, Long> durations = networkIngredients.getLastSecondDurationIndex();
×
77
                for (Map.Entry<PartPos, Long> durationEntry : durations.entrySet()) {
×
78
                    PartPos pos = durationEntry.getKey();
×
79
                    rawObservers.add(new RawObserverData(pos.getPos().getLevelKey(),
×
80
                            pos.getPos().getBlockPos(), pos.getSide(),
×
81
                            networkIngredients.getComponent().getName().toString(), durationEntry.getValue()));
×
82
                }
×
83
            }
84
        }
85

86
        RawNetworkData rawNetworkData = new RawNetworkData(network.isKilled(), network.hashCode(), network.getCablesCount(), rawParts, rawObservers);
×
87
        IntegratedDynamics._instance.getPacketHandler().sendToPlayer(new NetworkDiagnosticsNetworkPacket(rawNetworkData.toNbt()), player);
×
88
    }
×
89

90
    public synchronized void sendNetworkUpdate(INetwork network) {
91
        for (Iterator<UUID> it = players.iterator(); it.hasNext();) {
×
92
            UUID uuid = it.next();
×
93
            ServerPlayer player = getPlayer(uuid);
×
94
            if (player != null) {
×
95
                sendNetworkUpdateToPlayer(player, network);
×
96
            } else {
97
                it.remove();
×
98
            }
99
        }
×
100
    }
×
101

102
    public synchronized boolean isBeingDiagnozed() {
103
        return !players.isEmpty() || !activeMeasurements.isEmpty();
10!
104
    }
105

106
    /**
107
     * Check if a measurement session is complete for the given player.
108
     * @param playerId The UUID of the player
109
     * @return true if the measurement is complete, false otherwise
110
     */
111
    public synchronized boolean isMeasurementComplete(UUID playerId) {
112
        MeasurementSession session = activeMeasurements.get(playerId);
×
113
        if (session == null) {
×
114
            return false;
×
115
        }
116
        return session.isComplete();
×
117
    }
118

119
    /**
120
     * Get the total tick time in milliseconds for a completed measurement.
121
     * @param playerId The UUID of the player
122
     * @return The total tick time in milliseconds, or 0.0 if measurement not complete or not found
123
     */
124
    public synchronized double getMeasurementTotalTickTime(UUID playerId) {
125
        MeasurementSession session = activeMeasurements.get(playerId);
×
126
        if (session == null || !session.isComplete()) {
×
127
            return 0.0;
×
128
        }
129

130
        long totalNanoseconds = 0;
×
131
        for (Long time : session.getPartTimes().values()) {
×
132
            totalNanoseconds += time;
×
133
        }
×
134
        for (Long time : session.getObserverTimes().values()) {
×
135
            totalNanoseconds += time;
×
136
        }
×
137

138
        return totalNanoseconds / 1_000_000.0;
×
139
    }
140

141
    /**
142
     * Get the average tick time in milliseconds for a completed measurement.
143
     * @param playerId The UUID of the player
144
     * @return The average tick time per game tick in milliseconds, or 0.0 if measurement not complete or not found
145
     */
146
    public synchronized double getMeasurementAverageTickTime(UUID playerId) {
147
        MeasurementSession session = activeMeasurements.get(playerId);
×
148
        if (session == null || !session.isComplete()) {
×
149
            return 0.0;
×
150
        }
151

152
        long totalNanoseconds = 0;
×
153
        for (Long time : session.getPartTimes().values()) {
×
154
            totalNanoseconds += time;
×
155
        }
×
156
        for (Long time : session.getObserverTimes().values()) {
×
157
            totalNanoseconds += time;
×
158
        }
×
159

160
        double totalMilliseconds = totalNanoseconds / 1_000_000.0;
×
161
        int totalTicks = session.getDurationSeconds() * 20;
×
162
        return totalMilliseconds / totalTicks;
×
163
    }
164

165
    /**
166
     * Clear a measurement after it has been read.
167
     * This is useful for non-player measurements that persist until explicitly cleared.
168
     * @param measurementId The UUID of the measurement to clear
169
     */
170
    public synchronized void clearMeasurement(UUID measurementId) {
171
        activeMeasurements.remove(measurementId);
×
172
        playerMeasurements.remove(measurementId);
×
173
    }
×
174

175
    /**
176
     * Start a measurement session without a player.
177
     * This is useful for automated benchmarking where no player is online.
178
     * Measurements started this way will persist until explicitly cleared via {@link #clearMeasurement(UUID)}.
179
     * @param measurementId A unique identifier for the measurement (e.g., a UUID or string)
180
     * @param durationSeconds Duration of the measurement in seconds
181
     * @return The UUID used internally for tracking this measurement
182
     */
183
    public synchronized UUID startMeasurementWithoutPlayer(String measurementId, int durationSeconds) {
184
        UUID internalId = UUID.nameUUIDFromBytes(measurementId.getBytes());
×
185
        if (activeMeasurements.containsKey(internalId)) {
×
186
            throw new IllegalStateException(String.format("Measurement with id %s (%s) already exists", internalId, measurementId));
×
187
        }
188

189
        MeasurementSession session = new MeasurementSession(internalId, durationSeconds);
×
190
        activeMeasurements.put(internalId, session);
×
191
        // Note: NOT adding to playerMeasurements, so it won't be auto-removed
192
        return internalId;
×
193
    }
194

195
    /**
196
     * Start a measurement session for a player.
197
     * @param player The player initiating the measurement
198
     * @param durationSeconds Duration of the measurement in seconds
199
     */
200
    public synchronized void startMeasurement(ServerPlayer player, int durationSeconds) {
201
        UUID playerId = player.getUUID();
×
202

203
        // Check if this player already has an active measurement
204
        if (playerToMeasurement.containsKey(playerId)) {
×
205
            player.sendSystemMessage(Component.literal("A measurement is already running for you. Please wait for it to complete.")
×
206
                    .withStyle(ChatFormatting.RED));
×
207
            return;
×
208
        }
209

210
        UUID measurementUUID = startMeasurementWithoutPlayer("player:" + playerId, durationSeconds);
×
211

212
        if (measurementUUID != null) {
×
213
            playerMeasurements.add(measurementUUID); // Mark as player measurement for auto-removal
×
214
            playerToMeasurement.put(playerId, measurementUUID); // Track which measurement belongs to this player
×
215
        }
216

217
        player.sendSystemMessage(Component.literal("Started measuring network tick times for " + durationSeconds + " seconds...")
×
218
                .withStyle(ChatFormatting.GREEN));
×
219
    }
×
220

221
    /**
222
     * Accumulate tick times for active measurement sessions.
223
     */
224
    public synchronized void accumulateMeasurements() {
225
        if (activeMeasurements.isEmpty()) {
×
226
            return;
×
227
        }
228

229
        for (INetwork network : NetworkWorldStorage.Access.getInstance(IntegratedDynamics._instance).get().getNetworks()) {
×
230
            // Accumulate individual parts
231
            for (INetworkElement element : network.getElements()) {
×
232
                long elementDuration = network.getLastSecondDuration(element);
×
233
                if (elementDuration > 0 && element instanceof IPartNetworkElement) {
×
234
                    IPartNetworkElement partElement = (IPartNetworkElement) element;
×
235
                    PartPos pos = partElement.getTarget().getCenter();
×
236
                    String partName = partElement.getPart().getUniqueName().toString();
×
237
                    String dimension = pos.getPos().getLevelKey().identifier().toString();
×
238
                    int x = pos.getPos().getBlockPos().getX();
×
239
                    int y = pos.getPos().getBlockPos().getY();
×
240
                    int z = pos.getPos().getBlockPos().getZ();
×
241

242
                    PartInfo partInfo = new PartInfo(partName, dimension, x, y, z);
×
243

244
                    for (MeasurementSession session : activeMeasurements.values()) {
×
245
                        session.accumulate(partInfo, elementDuration);
×
246
                    }
×
247
                }
248
            }
×
249

250
            // Accumulate individual observers
251
            for (IFullNetworkListener fullNetworkListener : network.getFullNetworkListeners()) {
×
252
                if (fullNetworkListener instanceof IPositionedAddonsNetworkIngredients) {
×
253
                    IPositionedAddonsNetworkIngredients<?, ?> networkIngredients = (IPositionedAddonsNetworkIngredients<?, ?>) fullNetworkListener;
×
254
                    Map<PartPos, Long> durations = networkIngredients.getLastSecondDurationIndex();
×
255
                    String componentName = networkIngredients.getComponent().getName().toString();
×
256

257
                    for (Map.Entry<PartPos, Long> durationEntry : durations.entrySet()) {
×
258
                        long observerDuration = durationEntry.getValue();
×
259
                        if (observerDuration > 0) {
×
260
                            PartPos pos = durationEntry.getKey();
×
261
                            String dimension = pos.getPos().getLevelKey().identifier().toString();
×
262
                            int x = pos.getPos().getBlockPos().getX();
×
263
                            int y = pos.getPos().getBlockPos().getY();
×
264
                            int z = pos.getPos().getBlockPos().getZ();
×
265

266
                            ObserverInfo observerInfo = new ObserverInfo(componentName, dimension, x, y, z);
×
267

268
                            for (MeasurementSession session : activeMeasurements.values()) {
×
269
                                session.accumulateObserver(observerInfo, observerDuration);
×
270
                            }
×
271
                        }
272
                    }
×
273
                }
274
            }
275
        }
×
276

277
        // Increment the second counter for all sessions
278
        for (MeasurementSession session : activeMeasurements.values()) {
×
279
            session.incrementSecond();
×
280
        }
×
281
    }
×
282

283
    /**
284
     * Check and complete any finished measurements.
285
     * Player-based measurements are automatically removed after completion and results are sent.
286
     * Non-player measurements (e.g., from automated tests) persist until explicitly cleared.
287
     */
288
    public synchronized void checkCompleteMeasurements() {
289
        Iterator<Map.Entry<UUID, MeasurementSession>> it = activeMeasurements.entrySet().iterator();
×
290
        while (it.hasNext()) {
×
291
            Map.Entry<UUID, MeasurementSession> entry = it.next();
×
292
            UUID measurementId = entry.getKey();
×
293
            MeasurementSession session = entry.getValue();
×
294

295
            if (session.isComplete()) {
×
296
                // Only auto-remove player-based measurements
297
                if (playerMeasurements.contains(measurementId)) {
×
298
                    // Find the player who owns this measurement
299
                    UUID playerId = null;
×
300
                    for (Map.Entry<UUID, UUID> playerEntry : playerToMeasurement.entrySet()) {
×
301
                        if (playerEntry.getValue().equals(measurementId)) {
×
302
                            playerId = playerEntry.getKey();
×
303
                            break;
×
304
                        }
305
                    }
×
306

307
                    if (playerId != null) {
×
308
                        ServerPlayer player = getPlayer(playerId);
×
309
                        if (player != null) {
×
310
                            sendMeasurementResults(player, session);
×
311
                        }
312
                        playerToMeasurement.remove(playerId);
×
313
                    }
314

315
                    playerMeasurements.remove(measurementId);
×
316
                    it.remove();
×
317
                }
318
                // Non-player measurements persist for explicit reading
319
            }
320
        }
×
321
    }
×
322

323
    /**
324
     * Send measurement results to the player.
325
     */
326
    private void sendMeasurementResults(ServerPlayer player, MeasurementSession session) {
327
        if (session.getPartTimes().isEmpty() && session.getObserverTimes().isEmpty()) {
×
328
            player.sendSystemMessage(Component.literal("No network elements found during measurement period.")
×
329
                    .withStyle(ChatFormatting.YELLOW));
×
330
            return;
×
331
        }
332

333
        // Calculate totals
334
        long totalNanoseconds = 0;
×
335
        for (Long time : session.getPartTimes().values()) {
×
336
            totalNanoseconds += time;
×
337
        }
×
338
        for (Long time : session.getObserverTimes().values()) {
×
339
            totalNanoseconds += time;
×
340
        }
×
341

342
        double totalMilliseconds = totalNanoseconds / 1_000_000.0;
×
343
        double totalSeconds = totalNanoseconds / 1_000_000_000.0;
×
344

345
        // Format main result
346
        player.sendSystemMessage(Component.literal("=== Network Tick Time Measurement Results ===")
×
347
                .withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD));
×
348
        player.sendSystemMessage(Component.literal("Measurement Duration: " + session.getDurationSeconds() + " seconds")
×
349
                .withStyle(ChatFormatting.GRAY));
×
350
        player.sendSystemMessage(Component.literal(""));
×
351

352
        player.sendSystemMessage(Component.literal("Total Tick Time: ")
×
353
                .withStyle(ChatFormatting.AQUA)
×
354
                .append(Component.literal(String.format("%.2f ms (%.3f s)", totalMilliseconds, totalSeconds))
×
355
                        .withStyle(ChatFormatting.WHITE)));
×
356

357
        // Calculate average per tick (20 ticks per second)
358
        int totalTicks = session.getDurationSeconds() * 20;
×
359
        double avgPerTick = totalMilliseconds / totalTicks;
×
360
        player.sendSystemMessage(Component.literal("Average per Tick: ")
×
361
                .withStyle(ChatFormatting.AQUA)
×
362
                .append(Component.literal(String.format("%.4f ms", avgPerTick))
×
363
                        .withStyle(ChatFormatting.WHITE)));
×
364

365
        // Show top-5 network parts
366
        if (!session.getPartTimes().isEmpty()) {
×
367
            player.sendSystemMessage(Component.literal(""));
×
368
            player.sendSystemMessage(Component.literal("Top Network Parts:")
×
369
                    .withStyle(ChatFormatting.YELLOW));
×
370

371
            // Sort by time descending
372
            List<Map.Entry<PartInfo, Long>> sortedParts = Lists.newArrayList(session.getPartTimes().entrySet());
×
373
            sortedParts.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
×
374

375
            int count = 0;
×
376
            for (Map.Entry<PartInfo, Long> entry : sortedParts) {
×
377
                if (count >= 5) break;
×
378

379
                PartInfo info = entry.getKey();
×
380
                double partMs = entry.getValue() / 1_000_000.0;
×
381
                double percentage = (entry.getValue() * 100.0) / totalNanoseconds;
×
382
                player.sendSystemMessage(Component.literal(String.format("  %d. %s @ (%d, %d, %d) [%s]: %.2f ms (%.1f%%)",
×
383
                        count + 1, info.partName, info.x, info.y, info.z, info.dimension, partMs, percentage))
×
384
                        .withStyle(ChatFormatting.WHITE));
×
385
                count++;
×
386
            }
×
387
        }
388

389
        // Show top-3 observers if any exist
390
        if (!session.getObserverTimes().isEmpty()) {
×
391
            player.sendSystemMessage(Component.literal(""));
×
392
            player.sendSystemMessage(Component.literal("Top Observers:")
×
393
                    .withStyle(ChatFormatting.YELLOW));
×
394

395
            // Sort by time descending
396
            List<Map.Entry<ObserverInfo, Long>> sortedObservers = Lists.newArrayList(session.getObserverTimes().entrySet());
×
397
            sortedObservers.sort((a, b) -> Long.compare(b.getValue(), a.getValue()));
×
398

399
            int count = 0;
×
400
            for (Map.Entry<ObserverInfo, Long> entry : sortedObservers) {
×
401
                if (count >= 3) break;
×
402

403
                ObserverInfo info = entry.getKey();
×
404
                double obsMs = entry.getValue() / 1_000_000.0;
×
405
                double percentage = (entry.getValue() * 100.0) / totalNanoseconds;
×
406
                player.sendSystemMessage(Component.literal(String.format("  %d. %s @ (%d, %d, %d) [%s]: %.2f ms (%.1f%%)",
×
407
                        count + 1, info.componentName, info.x, info.y, info.z, info.dimension, obsMs, percentage))
×
408
                        .withStyle(ChatFormatting.WHITE));
×
409
                count++;
×
410
            }
×
411
        }
412

413
        player.sendSystemMessage(Component.literal("===========================================")
×
414
                .withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD));
×
415
    }
×
416

417
    /**
418
     * Internal class to track a measurement session.
419
     */
420
    private static class MeasurementSession {
421
        private final UUID playerId;
422
        private final int durationSeconds;
423
        private final Map<PartInfo, Long> partTimes = Maps.newHashMap();
×
424
        private final Map<ObserverInfo, Long> observerTimes = Maps.newHashMap();
×
425
        private int secondsAccumulated = 0;
×
426

427
        public MeasurementSession(UUID playerId, int durationSeconds) {
×
428
            this.playerId = playerId;
×
429
            this.durationSeconds = durationSeconds;
×
430
        }
×
431

432
        public void accumulate(PartInfo part, long durationNs) {
433
            partTimes.merge(part, durationNs, Long::sum);
×
434
        }
×
435

436
        public void accumulateObserver(ObserverInfo observer, long durationNs) {
437
            observerTimes.merge(observer, durationNs, Long::sum);
×
438
        }
×
439

440
        public void incrementSecond() {
441
            secondsAccumulated++;
×
442
        }
×
443

444
        public boolean isComplete() {
445
            return secondsAccumulated >= durationSeconds;
×
446
        }
447

448
        public Map<PartInfo, Long> getPartTimes() {
449
            return partTimes;
×
450
        }
451

452
        public Map<ObserverInfo, Long> getObserverTimes() {
453
            return observerTimes;
×
454
        }
455

456
        public int getDurationSeconds() {
457
            return durationSeconds;
×
458
        }
459
    }
460

461
    /**
462
     * Information about a network part for tracking.
463
     */
464
    private static class PartInfo {
465
        public final String partName;
466
        public final String dimension;
467
        public final int x, y, z;
468

469
        public PartInfo(String partName, String dimension, int x, int y, int z) {
×
470
            this.partName = partName;
×
471
            this.dimension = dimension;
×
472
            this.x = x;
×
473
            this.y = y;
×
474
            this.z = z;
×
475
        }
×
476

477
        @Override
478
        public boolean equals(Object o) {
479
            if (this == o) return true;
×
480
            if (o == null || getClass() != o.getClass()) return false;
×
481
            PartInfo partInfo = (PartInfo) o;
×
482
            return x == partInfo.x && y == partInfo.y && z == partInfo.z &&
×
483
                    partName.equals(partInfo.partName) && dimension.equals(partInfo.dimension);
×
484
        }
485

486
        @Override
487
        public int hashCode() {
488
            return (31 * (31 * (31 * (31 * partName.hashCode() + dimension.hashCode()) + x) + y) + z);
×
489
        }
490
    }
491

492
    /**
493
     * Information about a network observer for tracking.
494
     */
495
    private static class ObserverInfo {
496
        public final String componentName;
497
        public final String dimension;
498
        public final int x, y, z;
499

500
        public ObserverInfo(String componentName, String dimension, int x, int y, int z) {
×
501
            this.componentName = componentName;
×
502
            this.dimension = dimension;
×
503
            this.x = x;
×
504
            this.y = y;
×
505
            this.z = z;
×
506
        }
×
507

508
        @Override
509
        public boolean equals(Object o) {
510
            if (this == o) return true;
×
511
            if (o == null || getClass() != o.getClass()) return false;
×
512
            ObserverInfo that = (ObserverInfo) o;
×
513
            return x == that.x && y == that.y && z == that.z &&
×
514
                    componentName.equals(that.componentName) && dimension.equals(that.dimension);
×
515
        }
516

517
        @Override
518
        public int hashCode() {
519
            return (31 * (31 * (31 * (31 * componentName.hashCode() + dimension.hashCode()) + x) + y) + z);
×
520
        }
521
    }
522

523
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc