Best Practices
Souhrn doporučených postupů pro vývoj Hytale pluginů.
---
1. Thread Safety
VŽDY Dodržuj
// ✓ Používej AbstractPlayerCommand pro příkazy s komponenty
public class MyCommand extends AbstractPlayerCommand { }// ✓ Používej world.execute() pro async → sync
world.execute(() -> {
store.getComponent(ref, Type.getComponentType());
});
// ✓ Používej thread-safe kolekce
private final Map data = new ConcurrentHashMap<>();
// ✓ Používej EntityEventSystem pro ECS eventy
public class MySystem extends EntityEventSystem { }
NIKDY Nedělej
// ✗ CommandBase s komponentami
public class BadCommand extends CommandBase {
void executeSync(CommandContext ctx) {
store.getComponent(ref, Type.getComponentType()); // CRASH!
}
}// ✗ Blocking na world threadu
world.execute(() -> {
Thread.sleep(5000); // NIKDY!
database.save(); // Blocking I/O
});
// ✗ HashMap pro sdílená data
private final Map data = new HashMap<>(); // Thread-unsafe!
---
2. Null Safety
VŽDY Kontroluj Null
// ✓ Kontroluj výsledky getComponent()
Player player = store.getComponent(ref, Player.getComponentType());
if (player != null) {
player.sendMessage(msg);
}// ✓ Používej Optional pattern
Optional.ofNullable(store.getComponent(ref, Type.getComponentType()))
.ifPresent(comp -> { / use comp / });
NIKDY Nepředpokládej Existenci
// ✗ Přímé použití bez kontroly
Player player = store.getComponent(ref, Player.getComponentType());
player.sendMessage(msg); // NullPointerException!
---
3. Event Registrace
Sync Eventy
// ✓ PlayerReadyEvent, PlayerDisconnectEvent, atd.
getEventRegistry().registerGlobal(PlayerReadyEvent.class, event -> {
// Bezpečný přístup ke komponentám
});
Async Eventy
// ✓ PlayerChatEvent MUSÍ používat registerAsync
getEventRegistry().registerAsyncGlobal(PlayerChatEvent.class, future -> {
future.thenAccept(event -> {
// Pro komponenty použij world.execute()
});
});
ECS Eventy
// ✓ BreakBlockEvent, DeathEvent, atd.
getEntityStoreRegistry().registerSystem(new MyBlockSystem());// ✗ NEFUNGUJE
getEventRegistry().register(BreakBlockEvent.class, event -> { });
---
4. Struktura Pluginu
Singleton Pattern
public class MyPlugin extends JavaPlugin {
private static MyPlugin instance; public MyPlugin(JavaPluginInit init) {
super(init);
}
public static MyPlugin get() {
return instance;
}
@Override
protected void setup() {
instance = this;
// ...
}
}
Separace Odpovědností
MyPlugin/
├── MyPlugin.java # Hlavní třída, registrace
├── commands/
│ ├── MyCommand.java
│ └── AnotherCommand.java
├── systems/
│ ├── BlockBreakSystem.java
│ └── DeathSystem.java
├── managers/
│ ├── DataManager.java
│ └── ConfigManager.java
└── components/
└── MyComponent.java
---
5. Lifecycle
Setup Phase
@Override
protected void setup() {
instance = this; // 1. Komponenty
myComponentType = getEntityStoreRegistry().registerComponent(
MyComponent.class, MyComponent::new);
// 2. Systémy
getEntityStoreRegistry().registerSystem(new MySystem());
// 3. Příkazy
getCommandRegistry().registerCommand(new MyCommand());
// 4. Eventy
getEventRegistry().registerGlobal(PlayerReadyEvent.class, this::onReady);
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, e -> loadData());
}
Shutdown Phase
@Override
protected void shutdown() {
// 1. Zastav tasky
if (scheduler != null) scheduler.shutdown(); // 2. Ulož data SYNCHRONNĚ
saveAllData();
// 3. Cleanup
cleanup();
getLogger().atInfo().log("Plugin shutdown complete");
}
---
6. Async Operace
Správný Pattern
// Async I/O, sync aplikace
CompletableFuture.runAsync(() -> {
// Async: databáze, soubory, síť
PlayerData data = database.load(uuid); return data;
}).thenAccept(data -> {
// Stále async thread - pro komponenty:
world.execute(() -> {
applyData(player, data); // Bezpečné
});
});
Auto-Save Pattern
scheduler.scheduleAtFixedRate(() -> {
for (Player player : Universe.get().getPlayers()) {
CompletableFuture.runAsync(() -> {
savePlayerData(player.getUuid());
});
}
}, 5, 5, TimeUnit.MINUTES);
---
7. Logging
Používej Správné Úrovně
// Info - běžné operace
getLogger().atInfo().log("Loaded %d warps", count);// Warning - problémy (ne kritické)
getLogger().atWarning().log("Config not found, using defaults");
// Severe - chyby
getLogger().at(Level.SEVERE).withCause(e).log("Failed to save:");
Nepřeháněj Logging
// ✗ Příliš mnoho logů
for (Player p : players) {
getLogger().atInfo().log("Processing player " + p.getName());
}// ✓ Sumarizace
getLogger().atInfo().log("Processed %d players", players.size());
---
8. Konfigurace
Výchozí Hodnoty
public class Config {
private int saveInterval = 5; // Výchozí hodnota
private boolean debugMode = false; public void load(BsonDocument doc) {
if (doc.containsKey("saveInterval")) {
saveInterval = doc.getInt32("saveInterval").getValue();
}
// Pokud klíč chybí, zůstane výchozí hodnota
}
}
Validace
public void load(BsonDocument doc) {
int value = doc.getInt32("saveInterval").getValue(); if (value < 1 || value > 60) {
getLogger().atWarning().log("Invalid saveInterval %d, using default", value);
value = 5;
}
this.saveInterval = value;
}
---
9. Error Handling
Try-Catch v Kritických Místech
@Override
protected void shutdown() {
try {
saveAllData();
} catch (Exception e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to save data:");
}
}
Graceful Degradation
public void processPlayer(Player player) {
try {
doSomething(player);
} catch (Exception e) {
getLogger().atWarning().log("Failed for player %s: %s",
player.getName(), e.getMessage());
// Pokračuj s dalšími hráči
}
}
---
10. Performance
Minimalizuj Component Lookups
// ✗ Dvojí lookup
if (store.hasComponent(ref, Type.getComponentType())) {
MyComp comp = store.getComponent(ref, Type.getComponentType());
}// ✓ Jeden lookup
MyComp comp = store.getComponent(ref, Type.getComponentType());
if (comp != null) {
// use comp
}
Cachuj Co Můžeš
// ✗ Opakované volání
for (Player p : players) {
MyPlugin.get().getConfig().getValue(); // Každou iteraci
}// ✓ Cachování
int value = MyPlugin.get().getConfig().getValue();
for (Player p : players) {
// použij value
}
Batching
// ✗ Mnoho jednotlivých operací
for (Player p : players) {
savePlayer(p); // Sync I/O každou iteraci
}// ✓ Async batch
List> futures = players.stream()
.map(p -> CompletableFuture.runAsync(() -> savePlayer(p)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
---
Checklist
Před Commitem
- [ ] Thread safety: AbstractPlayerCommand, world.execute()
- [ ] Null checks: všechny getComponent()
- [ ] Event registrace: správný typ (sync/async/ECS)
- [ ] Shutdown: uložení dat, cleanup
- [ ] Logging: správné úrovně, ne příliš verbose
- [ ] Error handling: try-catch v kritických místech
- [ ] manifest.json: správné Dependencies
- [ ] Testováno na dev serveru
- [ ] Dokumentace aktualizována
- [ ] Verze inkrementována
Před Release
---
Quick Reference
| Situace | Řešení |
|---------|--------|
| Příkaz s komponenty | AbstractPlayerCommand |
| Async event | registerAsync() + world.execute() |
| ECS event | EntityEventSystem |
| Sdílená data | ConcurrentHashMap |
| Async I/O | CompletableFuture.runAsync() |
| Periodický task | ScheduledExecutorService |
| Shutdown | Synchronní save |