Dependencies
Detailní dokumentace k systému závislostí a verzování v Hytale pluginech.
---
Přehled
Dependency Types:
│
├── Dependencies (Povinné - plugin se nenačte bez nich)
│
├── OptionalDependencies (Volitelné - plugin funguje i bez nich)
│
└── LoadBefore (Pořadí načítání)
---
Semver (Semantic Versioning)
Hytale používá standardní semantic versioning:
public class Semver {
private final long major; // Breaking changes
private final long minor; // New features, backward compatible
private final long patch; // Bug fixes
@Nullable
private final String preRelease; // Pre-release tag (alpha, beta, rc)
@Nullable
private final String buildMetadata; // Build metadata public static Semver fromString(String str) {
// Parsuje "1.2.3", "1.2.3-alpha", "1.2.3-rc.1+build.123"
}
}
Formát Verze
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]Příklady:
1.0.0
1.2.3
2.0.0-alpha
2.0.0-beta.1
2.0.0-rc.1+build.456
Pravidla Verzování
| Změna | Význam | Příklad |
|-------|--------|---------|
| MAJOR | Breaking changes | 1.0.0 → 2.0.0 |
| MINOR | Nové funkce (zpětně kompatibilní) | 1.0.0 → 1.1.0 |
| PATCH | Bug fixy | 1.0.0 → 1.0.1 |
---
SemverRange
Definuje rozsah akceptovatelných verzí:
public class SemverRange implements SemverSatisfies {
public static final SemverRange WILDCARD = new SemverRange(new SemverSatisfies[0], true); private final SemverSatisfies[] comparators;
private final boolean and; // AND vs OR kombinace
@Override
public boolean satisfies(Semver semver) {
if (this.and) {
// Všechny musí být splněny
for (SemverSatisfies comparator : this.comparators) {
if (!comparator.satisfies(semver)) {
return false;
}
}
return true;
} else {
// Alespoň jedna musí být splněna
for (SemverSatisfies comparator : this.comparators) {
if (comparator.satisfies(semver)) {
return true;
}
}
return false;
}
}
}
---
Syntaxe SemverRange
Základní Operátory
| Syntaxe | Význam | Příklad |
|---------|--------|---------|
| | Jakákoliv verze | = všechny |
| 1.2.3 | Přesně tato verze | 1.2.3 |
| >1.2.3 | Větší než | >1.2.3 = 1.2.4+ |
| >=1.2.3 | Větší nebo rovno | >=1.2.3 = 1.2.3+ |
| <1.2.3 | Menší než | <1.2.3 = max 1.2.2 |
| <=1.2.3 | Menší nebo rovno | <=1.2.3 = max 1.2.3 |
Range Operátory
| Syntaxe | Význam | Ekvivalent |
|---------|--------|------------|
| 1.2.3 - 2.3.4 | Inclusive range | >=1.2.3 <=2.3.4 |
| ~1.2.3 | Patch-level changes | >=1.2.3 <1.3.0 |
| ^1.2.3 | Compatible with | >=1.2.3 <2.0.0 |
X-Range
| Syntaxe | Význam | Ekvivalent |
|---------|--------|------------|
| 1.x | Any minor | >=1.0.0 <2.0.0 |
| 1.2.x | Any patch | >=1.2.0 <1.3.0 |
| 1.* | Any minor | >=1.0.0 <2.0.0 |
OR Kombinace
1.0.0 || 2.0.0 // Verze 1.0.0 NEBO 2.0.0
>=1.0.0 || <0.5.0 // Verze 1.0.0+ NEBO pod 0.5.0
---
SemverRange Parsing
Interní implementace:
@Nonnull
public static SemverRange fromString(String str) {
str = str.trim(); // Wildcard
if (str.isBlank() || "*".equals(str)) {
return WILDCARD;
}
// OR kombinace
String[] split = str.split("\\|\\|");
SemverSatisfies[] comparators = new SemverSatisfies[split.length];
for (int i = 0; i < split.length; i++) {
String subRange = split[i].trim();
if (subRange.contains(" - ")) {
// Hyphen range: 1.2.3 - 2.3.4
String[] range = subRange.split(" - ");
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, Semver.fromString(range[0])),
new SemverComparator(ComparisonType.LTE, Semver.fromString(range[1]))
}, true);
}
else if (subRange.charAt(0) == '~') {
// Tilde range: ~1.2.3
Semver semver = Semver.fromString(subRange.substring(1));
// >=1.2.3 <1.3.0
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, semver),
new SemverComparator(ComparisonType.LT,
new Semver(semver.getMajor(), semver.getMinor() + 1, 0, null, null))
}, true);
}
else if (subRange.charAt(0) == '^') {
// Caret range: ^1.2.3
Semver semver = Semver.fromString(subRange.substring(1));
if (semver.getMajor() > 0) {
// >=1.2.3 <2.0.0
comparators[i] = new SemverRange(new SemverSatisfies[]{
new SemverComparator(ComparisonType.GTE, semver),
new SemverComparator(ComparisonType.LT,
new Semver(semver.getMajor() + 1, 0, 0, null, null))
}, true);
}
// ... special handling for 0.x versions
}
else if (hasPrefix(subRange)) {
// Comparator: >=1.2.3, >1.2.3, etc.
comparators[i] = SemverComparator.fromString(subRange);
}
// ...
}
return new SemverRange(comparators, false); // OR between ranges
}
---
Typy Závislostí
Dependencies (Povinné)
Plugin se nenačte pokud závislost chybí nebo nemá správnou verzi:
{
"Dependencies": {
"Hytale:DamageModule": "*",
"Hytale:TeleportPlugin": ">=1.0.0",
"MyCompany:CoreLib": "^2.0.0"
}
}
// Validace při načítání
for (Entry entry : manifest.getDependencies().entrySet()) {
PluginIdentifier id = entry.getKey();
SemverRange expectedVersion = entry.getValue(); PluginBase dependency = plugins.get(id);
if (dependency == null) {
throw new MissingPluginDependencyException("Dependency not found: " + id);
}
if (!dependency.getManifest().getVersion().satisfies(expectedVersion)) {
throw new MissingPluginDependencyException(
String.format("Version of dependency '%s'(%s) does not satisfy '%s'",
id, dependency.getManifest().getVersion(), expectedVersion)
);
}
}
OptionalDependencies (Volitelné)
Plugin se načte i bez těchto závislostí:
{
"OptionalDependencies": {
"Hytale:BedsPlugin": "*",
"ThirdParty:DiscordIntegration": ">=1.0.0"
}
}
Použití v kódu:
@Override
protected void setup() {
PluginManager pm = PluginManager.get(); // Kontrola volitelné závislosti
PluginBase bedsPlugin = pm.getPlugin(new PluginIdentifier("Hytale", "BedsPlugin"));
if (bedsPlugin != null && bedsPlugin.isEnabled()) {
// Integrace s BedsPlugin
setupBedsIntegration();
getLogger().info("BedsPlugin integration enabled");
}
// Nebo pomocí hasPlugin s verzí
if (pm.hasPlugin(new PluginIdentifier("ThirdParty", "DiscordIntegration"),
SemverRange.fromString(">=1.0.0"))) {
setupDiscordIntegration();
}
}
LoadBefore
Plugin se načte před specifikovanými pluginy:
{
"LoadBefore": {
"OtherPlugin:UISystem": "*"
}
}
Použití:
- Registrace event handlerů před jinými pluginy
- Inicializace sdílených služeb
- Nastavení defaultů před overrides
---
Load Order
PluginManager vypočítá pořadí načítání na základě:
1. Dependencies - Plugin musí být načten po svých závislostech
2. LoadBefore - Plugin musí být načten před specifikovanými
// Z PluginManager
this.loadOrder = PendingLoadPlugin.calculateLoadOrder(pending);for (PendingLoadPlugin pendingLoadPlugin : this.loadOrder) {
PluginBase plugin = pendingLoadPlugin.load();
if (plugin != null) {
this.plugins.put(plugin.getIdentifier(), plugin);
}
}
Příklad Load Order
Manifest A:
Dependencies: []Manifest B:
Dependencies: [A]
Manifest C:
Dependencies: [A, B]
LoadBefore: [D]
Manifest D:
Dependencies: [A]
Load Order: A → B → C → D
---
Server Version
Kontrola kompatibility se serverem:
{
"ServerVersion": ">=0.4.0 <1.0.0"
}
private void validatePluginDeps(PendingLoadPlugin plugin, ...) {
Semver serverVersion = ManifestUtil.getVersion();
SemverRange serverVersionRange = plugin.getManifest().getServerVersion(); if (serverVersionRange != null && serverVersion != null) {
if (!serverVersionRange.satisfies(serverVersion)) {
throw new MissingPluginDependencyException(
String.format("Failed to load '%s' because version of server does not satisfy '%s'!",
plugin.getIdentifier(), serverVersion)
);
}
}
}
---
Vestavěné Moduly Hytale
Hytale:DamageModule
{
"Dependencies": {
"Hytale:DamageModule": "*"
}
}
Poskytuje:
DamageEventDeathEventHytale:TeleportPlugin
{
"Dependencies": {
"Hytale:TeleportPlugin": "*"
}
}
Poskytuje:
/spawn, /tp, /warp příkazyTeleportHistory komponentaHytale:BedsPlugin
{
"Dependencies": {
"Hytale:BedsPlugin": "*"
}
}
Poskytuje:
Hytale:EntityModule
{
"Dependencies": {
"Hytale:EntityModule": "*"
}
}
Poskytuje:
---
Cyklické Závislosti
Cyklické závislosti jsou zakázány a způsobí chybu:
A → B → C → A (CHYBA!)
PluginManager detekuje cykly při výpočtu load order.
---
Runtime Operace
Načtení Pluginu
PluginManager pm = PluginManager.get();// Načíst plugin
boolean success = pm.load(new PluginIdentifier("MyCompany", "MyPlugin"));
// Plugin musí mít splněné všechny závislosti
Vyčtení Pluginu
// Unload (odpojí a vyčistí)
boolean success = pm.unload(new PluginIdentifier("MyCompany", "MyPlugin"));
Reload Pluginu
// Unload + Load
boolean success = pm.reload(new PluginIdentifier("MyCompany", "MyPlugin"));
Kontrola Závislosti
PluginManager pm = PluginManager.get();// Má plugin s verzí?
boolean hasPlugin = pm.hasPlugin(
new PluginIdentifier("Hytale", "TeleportPlugin"),
SemverRange.fromString(">=1.0.0")
);
// Získat plugin
PluginBase plugin = pm.getPlugin(new PluginIdentifier("Hytale", "TeleportPlugin"));
if (plugin != null && plugin.isEnabled()) {
// Použij plugin
}
---
Class Loading
Pluginy mohou přistupovat k třídám svých závislostí:
// PluginBridgeClassLoader
public Class> loadClass0(String name, PluginClassLoader pluginClassLoader, PluginManifest manifest) {
// 1. Nejprve hledej v Dependencies
for (PluginIdentifier id : manifest.getDependencies().keySet()) {
PluginBase pluginBase = plugins.get(id);
Class> loadClass = tryGetClass(name, pluginClassLoader, pluginBase);
if (loadClass != null) {
return loadClass;
}
} // 2. Pak v OptionalDependencies
for (PluginIdentifier id : manifest.getOptionalDependencies().keySet()) {
if (!manifest.getDependencies().containsKey(id)) {
PluginBase pluginBase = plugins.get(id);
if (pluginBase != null) {
Class> loadClass = tryGetClass(name, pluginClassLoader, pluginBase);
if (loadClass != null) {
return loadClass;
}
}
}
}
// 3. Nakonec v ostatních pluginech
for (Entry entry : plugins.entrySet()) {
if (!manifest.getDependencies().containsKey(entry.getKey()) &&
!manifest.getOptionalDependencies().containsKey(entry.getKey())) {
Class> loadClass = tryGetClass(name, pluginClassLoader, entry.getValue());
if (loadClass != null) {
return loadClass;
}
}
}
throw new ClassNotFoundException();
}
---
Best Practices
1. Používej SemverRange Správně
// Špatně - příliš striktní
{
"Dependencies": {
"SomePlugin": "1.2.3" // Jen přesně 1.2.3
}
}// Lépe - kompatibilní verze
{
"Dependencies": {
"SomePlugin": "^1.2.3" // 1.2.3 až <2.0.0
}
}
// Ještě lépe - minimální verze
{
"Dependencies": {
"SomePlugin": ">=1.2.3" // 1.2.3 nebo novější
}
}
2. Kontroluj Optional Dependencies
@Override
protected void setup() {
// VŽDY kontroluj null
PluginBase optDep = PluginManager.get().getPlugin(
new PluginIdentifier("Optional", "Feature")
); if (optDep != null && optDep.isEnabled()) {
// Bezpečné použití
}
}
3. Dokumentuj Závislosti
{
"Description": "My Plugin - requires DamageModule for combat features, optionally uses BedsPlugin for spawn points",
"Dependencies": {
"Hytale:DamageModule": "*"
},
"OptionalDependencies": {
"Hytale:BedsPlugin": "*"
}
}
---
Shrnutí
| Typ | Chování | Použití |
|-----|---------|---------|
| Dependencies | Povinné, blokující | Nezbytné funkce |
| OptionalDependencies | Volitelné | Rozšíření funkcí |
| LoadBefore | Pořadí načítání | Inicializace před jinými |
| ServerVersion | Kompatibilita serveru | API verze |
| Operátor | Syntaxe | Příklad |
|----------|---------|---------|
| Wildcard | * | Všechny verze |
| Greater | > | >1.0.0 |
| Greater/Equal | >= | >=1.0.0 |
| Less | < | <2.0.0 |
| Less/Equal | <= | <=2.0.0 |
| Tilde | ~ | ~1.2.3 = >=1.2.3 <1.3.0 |
| Caret | ^ | ^1.2.3 = >=1.2.3 <2.0.0 |
| Range | - | 1.0.0 - 2.0.0 |
| OR | \|\| | 1.0.0 \|\| 2.0.0 |