geforkt von Mirrors/FastAsyncWorldEdit
Trim performance (#451)
* Increase performance slightly when trimming. If the chunk section is all one blocks (common in plotworlds) it'll be a nice little boost. * Cache whether blocks are ticking or not. Greatly reduces the time required to create a palette * collapse 5 lines to 2. * Also apply to 14 and 15 for the numpties * Cleanup Actually ignore the exception - remove my debug print. Remove double semi-colon * Apparently 1.14/15 matter too still.
Dieser Commit ist enthalten in:
Ursprung
a2b0a5e622
Commit
56972ee40b
@ -4,6 +4,7 @@ import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
|||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.world.block.BlockID;
|
import com.sk89q.worldedit.world.block.BlockID;
|
||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockTypesCache;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -49,6 +50,8 @@ public class NMSAdapter {
|
|||||||
int air = 0;
|
int air = 0;
|
||||||
int num_palette = 0;
|
int num_palette = 0;
|
||||||
char[] getArr = null;
|
char[] getArr = null;
|
||||||
|
char lastOrdinal = BlockID.__RESERVED__;
|
||||||
|
boolean lastticking = false;
|
||||||
for (int i = 0; i < 4096; i++) {
|
for (int i = 0; i < 4096; i++) {
|
||||||
char ordinal = set[i];
|
char ordinal = set[i];
|
||||||
switch (ordinal) {
|
switch (ordinal) {
|
||||||
@ -75,8 +78,16 @@ public class NMSAdapter {
|
|||||||
air++;
|
air++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
BlockState state = BlockState.getFromOrdinal(ordinal);
|
boolean ticking;
|
||||||
if (state.getMaterial().isTicksRandomly()) {
|
if (ordinal != lastOrdinal) {
|
||||||
|
ticking = BlockTypesCache.ticking[ordinal];
|
||||||
|
lastOrdinal = ordinal;
|
||||||
|
lastticking = ticking;
|
||||||
|
} else {
|
||||||
|
ticking = lastticking;
|
||||||
|
}
|
||||||
|
if (ticking) {
|
||||||
|
BlockState state = BlockState.getFromOrdinal(ordinal);
|
||||||
ticking_blocks.put(BlockVector3.at(i & 15, (i >> 8) & 15, (i >> 4) & 15),
|
ticking_blocks.put(BlockVector3.at(i & 15, (i >> 8) & 15, (i >> 4) & 15),
|
||||||
WorldEditPlugin.getInstance().getBukkitImplAdapter()
|
WorldEditPlugin.getInstance().getBukkitImplAdapter()
|
||||||
.getInternalBlockStateId(state).orElse(0));
|
.getInternalBlockStateId(state).orElse(0));
|
||||||
|
@ -5,6 +5,7 @@ import static org.slf4j.LoggerFactory.getLogger;
|
|||||||
import com.boydti.fawe.Fawe;
|
import com.boydti.fawe.Fawe;
|
||||||
import com.boydti.fawe.FaweCache;
|
import com.boydti.fawe.FaweCache;
|
||||||
import com.boydti.fawe.beta.IChunkSet;
|
import com.boydti.fawe.beta.IChunkSet;
|
||||||
|
import com.boydti.fawe.beta.implementation.blocks.CharBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
||||||
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
||||||
@ -620,7 +621,37 @@ public class BukkitGetBlocks_1_14 extends CharGetBlocks {
|
|||||||
if (aggressive) {
|
if (aggressive) {
|
||||||
sections = null;
|
sections = null;
|
||||||
nmsChunk = null;
|
nmsChunk = null;
|
||||||
|
return super.trim(true);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (!hasSection(i) || super.sections[i] == CharBlocks.EMPTY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChunkSection existing = getSections()[i];
|
||||||
|
try {
|
||||||
|
final DataPaletteBlock<IBlockData> blocksExisting = existing.getBlocks();
|
||||||
|
|
||||||
|
final DataPalette<IBlockData> palette = (DataPalette<IBlockData>) BukkitAdapter_1_14.fieldPalette.get(blocksExisting);
|
||||||
|
int paletteSize;
|
||||||
|
|
||||||
|
if (palette instanceof DataPaletteLinear) {
|
||||||
|
paletteSize = ((DataPaletteLinear<IBlockData>) palette).b();
|
||||||
|
} else if (palette instanceof DataPaletteHash) {
|
||||||
|
paletteSize = ((DataPaletteHash<IBlockData>) palette).b();
|
||||||
|
} else {
|
||||||
|
super.trim(false, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (paletteSize == 1) {
|
||||||
|
//If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
super.trim(false, i);
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
super.trim(false, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.trim(aggressive);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import static org.slf4j.LoggerFactory.getLogger;
|
|||||||
import com.boydti.fawe.Fawe;
|
import com.boydti.fawe.Fawe;
|
||||||
import com.boydti.fawe.FaweCache;
|
import com.boydti.fawe.FaweCache;
|
||||||
import com.boydti.fawe.beta.IChunkSet;
|
import com.boydti.fawe.beta.IChunkSet;
|
||||||
|
import com.boydti.fawe.beta.implementation.blocks.CharBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
||||||
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
||||||
@ -640,7 +641,37 @@ public class BukkitGetBlocks_1_15 extends CharGetBlocks {
|
|||||||
if (aggressive) {
|
if (aggressive) {
|
||||||
sections = null;
|
sections = null;
|
||||||
nmsChunk = null;
|
nmsChunk = null;
|
||||||
|
return super.trim(true);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (!hasSection(i) || super.sections[i] == CharBlocks.EMPTY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChunkSection existing = getSections()[i];
|
||||||
|
try {
|
||||||
|
final DataPaletteBlock<IBlockData> blocksExisting = existing.getBlocks();
|
||||||
|
|
||||||
|
final DataPalette<IBlockData> palette = (DataPalette<IBlockData>) BukkitAdapter_1_15.fieldPalette.get(blocksExisting);
|
||||||
|
int paletteSize;
|
||||||
|
|
||||||
|
if (palette instanceof DataPaletteLinear) {
|
||||||
|
paletteSize = ((DataPaletteLinear<IBlockData>) palette).b();
|
||||||
|
} else if (palette instanceof DataPaletteHash) {
|
||||||
|
paletteSize = ((DataPaletteHash<IBlockData>) palette).b();
|
||||||
|
} else {
|
||||||
|
super.trim(false, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (paletteSize == 1) {
|
||||||
|
//If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
super.trim(false, i);
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
super.trim(false, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.trim(aggressive);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.boydti.fawe.bukkit.adapter.mc1_15_2;
|
|||||||
import com.boydti.fawe.Fawe;
|
import com.boydti.fawe.Fawe;
|
||||||
import com.boydti.fawe.FaweCache;
|
import com.boydti.fawe.FaweCache;
|
||||||
import com.boydti.fawe.beta.IChunkSet;
|
import com.boydti.fawe.beta.IChunkSet;
|
||||||
|
import com.boydti.fawe.beta.implementation.blocks.CharBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks;
|
||||||
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
|
||||||
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
import com.boydti.fawe.bukkit.adapter.DelegateLock;
|
||||||
@ -644,7 +645,37 @@ public class BukkitGetBlocks_1_15_2 extends CharGetBlocks {
|
|||||||
if (aggressive) {
|
if (aggressive) {
|
||||||
sections = null;
|
sections = null;
|
||||||
nmsChunk = null;
|
nmsChunk = null;
|
||||||
|
return super.trim(true);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (!hasSection(i) || super.sections[i] == CharBlocks.EMPTY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ChunkSection existing = getSections()[i];
|
||||||
|
try {
|
||||||
|
final DataPaletteBlock<IBlockData> blocksExisting = existing.getBlocks();
|
||||||
|
|
||||||
|
final DataPalette<IBlockData> palette = (DataPalette<IBlockData>) BukkitAdapter_1_15_2.fieldPalette.get(blocksExisting);
|
||||||
|
int paletteSize;
|
||||||
|
|
||||||
|
if (palette instanceof DataPaletteLinear) {
|
||||||
|
paletteSize = ((DataPaletteLinear<IBlockData>) palette).b();
|
||||||
|
} else if (palette instanceof DataPaletteHash) {
|
||||||
|
paletteSize = ((DataPaletteHash<IBlockData>) palette).b();
|
||||||
|
} else {
|
||||||
|
super.trim(false, i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (paletteSize == 1) {
|
||||||
|
//If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
super.trim(false, i);
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
super.trim(false, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return super.trim(aggressive);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,4 +145,9 @@ public class CombinedBlocks implements IBlocks {
|
|||||||
public boolean trim(boolean aggressive) {
|
public boolean trim(boolean aggressive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ public interface IBlocks extends Trimable {
|
|||||||
.map(layer -> (1 << layer)).sum();
|
.map(layer -> (1 << layer)).sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean trim(boolean aggressive, int layer);
|
||||||
|
|
||||||
IBlocks reset();
|
IBlocks reset();
|
||||||
|
|
||||||
default byte[] toByteArray(boolean full) {
|
default byte[] toByteArray(boolean full) {
|
||||||
|
@ -153,4 +153,9 @@ public class BitSetBlocks implements IChunkSet {
|
|||||||
public boolean trim(boolean aggressive) {
|
public boolean trim(boolean aggressive) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,17 @@ public abstract class CharBlocks implements IBlocks {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
boolean result = true;
|
||||||
|
if (sections[layer] == EMPTY && blocks[layer] != null) {
|
||||||
|
blocks[layer] = null;
|
||||||
|
} else {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkSet reset() {
|
public IChunkSet reset() {
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
|
@ -25,6 +25,13 @@ public abstract class CharGetBlocks extends CharBlocks implements IChunkGet {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
sections[layer] = EMPTY;
|
||||||
|
blocks[layer] = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IChunkSet reset() {
|
public IChunkSet reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
|
@ -85,6 +85,11 @@ public class FallbackChunkGet implements IChunkGet {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends Future<T>> T call(IChunkSet set, Runnable finalize) {
|
public <T extends Future<T>> T call(IChunkSet set, Runnable finalize) {
|
||||||
for (int layer = 0; layer < 16; layer++) {
|
for (int layer = 0; layer < 16; layer++) {
|
||||||
|
@ -50,6 +50,10 @@ object NullChunkGet : IChunkGet {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun trim(aggressive: Boolean, layer: Int): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun <T : Future<T>> call(set: IChunkSet, finalize: Runnable): T? {
|
override fun <T : Future<T>> call(set: IChunkSet, finalize: Runnable): T? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -344,6 +344,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return this.trim(aggressive);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return chunkSet == null || chunkSet.isEmpty();
|
return chunkSet == null || chunkSet.isEmpty();
|
||||||
|
@ -117,5 +117,9 @@ object NullChunk : IQueueChunk<Nothing> {
|
|||||||
override fun trim(aggressive: Boolean): Boolean {
|
override fun trim(aggressive: Boolean): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun trim(aggressive: Boolean, layer: Int): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import com.boydti.fawe.beta.IBatchProcessor;
|
|||||||
import com.boydti.fawe.beta.IChunk;
|
import com.boydti.fawe.beta.IChunk;
|
||||||
import com.boydti.fawe.beta.IChunkGet;
|
import com.boydti.fawe.beta.IChunkGet;
|
||||||
import com.boydti.fawe.beta.IChunkSet;
|
import com.boydti.fawe.beta.IChunkSet;
|
||||||
import com.sk89q.worldedit.extent.Extent;
|
|
||||||
|
|
||||||
public class BatchProcessorHolder implements IBatchProcessorHolder {
|
public class BatchProcessorHolder implements IBatchProcessorHolder {
|
||||||
private IBatchProcessor processor = EmptyBatchProcessor.INSTANCE;
|
private IBatchProcessor processor = EmptyBatchProcessor.INSTANCE;
|
||||||
|
@ -549,6 +549,11 @@ public class MCAChunk implements IChunk {
|
|||||||
return isEmpty();
|
return isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean trim(boolean aggressive, int layer) {
|
||||||
|
return hasSection(layer);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompoundTag getEntity(UUID uuid) {
|
public CompoundTag getEntity(UUID uuid) {
|
||||||
return this.entities.get(uuid);
|
return this.entities.get(uuid);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.sk89q.worldedit.world.block;
|
package com.sk89q.worldedit.world.block;
|
||||||
|
|
||||||
import com.boydti.fawe.util.MathMan;
|
import com.boydti.fawe.util.MathMan;
|
||||||
|
import com.google.common.primitives.Booleans;
|
||||||
import com.sk89q.worldedit.WorldEdit;
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
import com.sk89q.worldedit.extension.platform.Capability;
|
import com.sk89q.worldedit.extension.platform.Capability;
|
||||||
import com.sk89q.worldedit.extension.platform.Platform;
|
import com.sk89q.worldedit.extension.platform.Platform;
|
||||||
@ -166,12 +167,14 @@ public class BlockTypesCache {
|
|||||||
|
|
||||||
public static final BlockType[] values;
|
public static final BlockType[] values;
|
||||||
public static final BlockState[] states;
|
public static final BlockState[] states;
|
||||||
|
public static final boolean[] ticking;
|
||||||
|
|
||||||
protected static final Set<String> $NAMESPACES = new LinkedHashSet<>();
|
protected static final Set<String> $NAMESPACES = new LinkedHashSet<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
ArrayList<BlockState> stateList = new ArrayList<>();
|
ArrayList<BlockState> stateList = new ArrayList<>();
|
||||||
|
ArrayList<Boolean> tickList = new ArrayList<>();
|
||||||
|
|
||||||
Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS);
|
Platform platform = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS);
|
||||||
Registries registries = platform.getRegistries();
|
Registries registries = platform.getRegistries();
|
||||||
@ -202,7 +205,7 @@ public class BlockTypesCache {
|
|||||||
if (values[internalId] != null) {
|
if (values[internalId] != null) {
|
||||||
throw new IllegalStateException("Invalid duplicate id for " + field.getName());
|
throw new IllegalStateException("Invalid duplicate id for " + field.getName());
|
||||||
}
|
}
|
||||||
BlockType type = register(defaultState, internalId, stateList);
|
BlockType type = register(defaultState, internalId, stateList, tickList);
|
||||||
// Note: Throws IndexOutOfBoundsError if nothing is registered and blocksMap is empty
|
// Note: Throws IndexOutOfBoundsError if nothing is registered and blocksMap is empty
|
||||||
values[internalId] = type;
|
values[internalId] = type;
|
||||||
}
|
}
|
||||||
@ -214,7 +217,7 @@ public class BlockTypesCache {
|
|||||||
String defaultState = entry.getValue();
|
String defaultState = entry.getValue();
|
||||||
// Skip already registered ids
|
// Skip already registered ids
|
||||||
for (; values[internalId] != null; internalId++);
|
for (; values[internalId] != null; internalId++);
|
||||||
BlockType type = register(defaultState, internalId, stateList);
|
BlockType type = register(defaultState, internalId, stateList, tickList);
|
||||||
values[internalId] = type;
|
values[internalId] = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +226,7 @@ public class BlockTypesCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
states = stateList.toArray(new BlockState[stateList.size()]);
|
states = stateList.toArray(new BlockState[stateList.size()]);
|
||||||
|
ticking = Booleans.toArray(tickList);
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -231,12 +234,14 @@ public class BlockTypesCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockType register(final String id, int internalId, List<BlockState> states) {
|
private static BlockType register(final String id, int internalId, List<BlockState> states, List<Boolean> tickList) {
|
||||||
// Get the enum name (remove namespace if minecraft:)
|
// Get the enum name (remove namespace if minecraft:)
|
||||||
int propStart = id.indexOf('[');
|
int propStart = id.indexOf('[');
|
||||||
String typeName = id.substring(0, propStart == -1 ? id.length() : propStart);
|
String typeName = id.substring(0, propStart == -1 ? id.length() : propStart);
|
||||||
String enumName = (typeName.startsWith("minecraft:") ? typeName.substring(10) : typeName).toUpperCase(Locale.ROOT);
|
String enumName = (typeName.startsWith("minecraft:") ? typeName.substring(10) : typeName).toUpperCase(Locale.ROOT);
|
||||||
|
int oldsize = states.size();
|
||||||
BlockType existing = new BlockType(id, internalId, states);
|
BlockType existing = new BlockType(id, internalId, states);
|
||||||
|
tickList.addAll(Collections.nCopies(states.size() - oldsize, existing.getMaterial().isTicksRandomly()));
|
||||||
// register states
|
// register states
|
||||||
BlockType.REGISTRY.register(typeName, existing);
|
BlockType.REGISTRY.register(typeName, existing);
|
||||||
String nameSpace = typeName.substring(0, typeName.indexOf(':'));
|
String nameSpace = typeName.substring(0, typeName.indexOf(':'));
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren