HyCodeYourTale

Best Practices

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
  • Před Release

  • [ ] manifest.json: správné Dependencies

  • [ ] Testováno na dev serveru

  • [ ] Dokumentace aktualizována

  • [ ] Verze inkrementována

---

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 |

Last updated: 20. ledna 2026