Jump to content

Bektor

Forge Modder
  • Posts

    852
  • Joined

  • Last visited

Posts posted by Bektor

  1. Just now, diesieben07 said:

    Show how you create the zip file system. The dot always stands for "current directory", even in windows and my local tests confirm this.

              
    // it is the same path as from the posts before, just in the backup thread which is located in another class, so I'm storing within the
    // thread a local reference/variable
    this.directionary = path;
            this.directionary.resolve("test.zip");
    
    [...]
    
    private void createZipFile() {
            // Delete the file if it already exists
            try {
                if(Files.deleteIfExists(this.directionary))
                    ServerUtilities.LOGGER.info("Deleted file '" + this.directionary.getFileName().toString() + "'.");
            } catch (IOException e) {
                ServerUtilities.LOGGER.fatal("Tried to delete an old backup, but failed. Backup: " + this.directionary.getFileName().toString(),  e);
            }
            
            try(FileSystem zipFile = FileSystems.newFileSystem(this.directionary, null)) {
                Files.copy(this.world, this.directionary, StandardCopyOption.REPLACE_EXISTING);
            } catch(IOException e) {
            
            }
        }

     

  2. 6 minutes ago, diesieben07 said:

    Cool. And? There is still nothing wrong with the dot.

    If I leave the dot where it is the whole code will run into an exception at the first try to create a file within my backups directionary as it does not exist because windows doesn't permit a directory with a dot within its name. (Thought the code to create the directory doesn't throw the exception, the one who tries to create a file within the directiory does however.)

     

    Quote
    
    java.nio.file.FileSystemNotFoundException: P:\Java\ForgeMods\run\assets\.\backups
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at com.sun.nio.zipfs.ZipFileSystem.<init>(ZipFileSystem.java:120)
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:139)
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at java.nio.file.FileSystems.newFileSystem(FileSystems.java:390)
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at minecraftplaye.serverutilities.backups.ThreadBackups.createZipFile(ThreadBackups.java:72)
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at minecraftplaye.serverutilities.backups.ThreadBackups.run(ThreadBackups.java:54)
    [23:01:54] [World Backup Thread/INFO] [STDERR]: at java.lang.Thread.run(Thread.java:748)

     

    Interestingly enough it is an FileSystemNotFoundException while the backups directory in which it tries to write doesn't even exist.

     

    try(FileSystem zipFile = FileSystems.newFileSystem(this.path.resolve("test.zip", null)) {
  3. 3 minutes ago, diesieben07 said:

    Do not worry about the dot, it denotes "current directory". You do not have to remove it.

    Oh, I do.

    At some point in my code this code is called:

    path.resolve(ModConfig.backupDir);

    It adds the backupDir to my path so it can be created in the next line and afterwards the whole path is passed onto the backup thread to do the backup.

  4. On 1/21/2018 at 6:55 PM, diesieben07 said:

    MinecraftServer::getDataDirectory

    Hm, I'm really wondering why this method returns me such a path:

     

    Path path = server.getDataDirectory().toPath();
    System.out.println(path.toAbsolutePath().toString());

    Output: 

    P:\Java\ForgeMods\run\assets\.

     

    Notice the dot at the end of the path.

    I mean, there is no folder with a "." in it's name within my file system (Windows 10, "virtual" NTFS drive [which basically means that P: is some kind of link to a specific folder within my file system]).

     

    Can I just remove the dot at the end of the path without risk that it might be different in the release-jar?

  5. On 1/21/2018 at 6:55 PM, diesieben07 said:

    WorldServer::getChunkSaveLocation.

     

    Ok, I tried this, but to get access to this method I need to have an instance of the world and a dimensionID, but I want to copy the whole world folder with all dimensions and all other files in there into the backup folder.

     

    @Nonnull
    private Path world = Paths.get(Backups.INSTANCE.server.getWorld(int dimensionID).getChunkSaveLocation());

     

  6. 50 minutes ago, diesieben07 said:

    I would suggest this method:

    1. Do what /save-all flush does.
    2. Do what /save-off does.
    3. Perform your backup.
    4. Do what /save-on does.

    Where can I find what these commands do?

     

    EDIT:

    And what is the difference between flushToDisk and saveAllChunks?

  7. On 1/21/2018 at 6:55 PM, diesieben07 said:

    WorldServer::getChunkSaveLocation.

     

    MinecraftServer::getDataDirectory.

    Thx, thought I ran into another question. While implementing the logic for a backup I stumbled across the variable disableLevelSaving. I'm wondering if

    this has to be set to true before I can save all chunks to disk for the backup to occur and wether I should reset it afterwards where I'm not quite sure how I would do this as

    my current logic is this:

    • save player data to disk
    • save world data to disk (foreach loop withing a try-catch block for every world/dimension)
    • start the backup while copying all files over

     

  8. 22 hours ago, jabelar said:

    Well here are some general best practices about performance optimization, which may not all apply to your case but worth thinking about:

    1) Profile before spending a lot of time with optimizations. Don't spend time optimizing stuff that doesn't have significant impact in overall performance. I often see people make some clever, usually less "readable" and more bug-prone, optimization where it isn't needed. You want readability and simplicity (for avoiding bugs) in your code when you can, so only compromise that where performance requires it.

     

    2) When nesting conditions or loops, when having conditions that && together always test the least likely (or the thing most likely to cause you to exit) thing first. For example, testing if world is remote and then testing if player is holding sword is backwards because it would be much better to exit quickly the 99.9% time the player is not holding the sword. Basically always look for ways to exit loops and methods as early as possible.

     

    3) When testing conditions that || together always test the most likely (or the thing most likely to cause you to continue). In the previous example, if you wanted to continue if the world was remote or the player was holding the sword you should test for world is remote first.

     

    4) In inner loops it is usually where it is worth coding super optimized. For example, in your code where you have the offsets for each of the enum facing, there may be a benefit for just directly adding 1 instead. Modern compilers are super optimized though so you should experiment. But I generally feel that you want to "flatten" the inner loop so call as few methods and conversions as possible.

     

    20 hours ago, Draco18s said:

    You want a performance improvement on A*?

    Jump Point Search. The [2] reference is a great resource as well.

    Thx for the answers. Those things should definitly help. ;)

     

    Thought I'm also looking into how it is possible to

    • test an A* algorithm in general using JUnit.
    • test this specific A* algorithm using JUnit to avoid the need of starting Minecraft.
  9. Hi there,

     

    I've implemented myself the A* algorithm (mainly just for the purpose of learning) as seen below and looking for a way to test the code with JUnit and improvements.

    I'm also looking for reviews on best-practice and performance!

     

    Just to note, the implementation works in 2D space within the 3D world of Minecraft.

     

    public class AStar {
        
        /**
         * A {@link HashSet} of {@link Node} which holds all nodes which have been examined.
         * Some may refer to this as the <code>interior</code>.
         */
        private HashSet<Node> closedSet = new HashSet<>();
        
        /**
         * A {@link PriorityQueue} of {@link Node} which holds all candidates for the algorithm to explore.
         * Some may refer to this as the <code>openList</code>.
         */
        private PriorityQueue<Node> frontier = new PriorityQueue<>(
                (firstNode, secondNode) -> {
                    if(firstNode.priority > secondNode.priority) return 1;
                    if(firstNode.priority < secondNode.priority) return -1;
                    return 0;
                });
        
        /**
         * A {@link HashSet} of {@link BlockPos} which holds all blocks the algorithm can move through.
         */
        private HashSet<BlockPos> passableBlocks = new HashSet<>();
        
        /**
         * Initialises this class with a list containing the 'level' the algorithm
         * uses to find the way.
         * 
         * @param passableBlocks All blocks the algorithm can use.
         */
        public AStar(HashSet<BlockPos> passableBlocks) {
            this.passableBlocks.addAll(passableBlocks);
        }
        
        /**
         * A* implementation
         * 
         * @param start
         * @param end
         * @return null if no path was found or the path as a list
         */
        public List<Node> getShortestPath(BlockPos start, BlockPos end) {
            if(start == null || end == null || !passableBlocks.contains(start) || !passableBlocks.contains(end))
                return null;
            
            this.frontier.add(new Node(start, null, 0, this.computeHeuristic(start, end)));
            
            while(!this.frontier.isEmpty()) {
                Node current = this.frontier.poll();
                if(current.location.equals(end)) {
                    List<Node> path = new ArrayList<>();
                    while(current.parent != null) {
                        path.add(current);
                        current = current.parent;
                    }
                    this.frontier.clear();
                    this.closedSet.clear();
                    return path;
                }
                this.closedSet.add(current);
                
                this.getAdjacentNodes(current, end);
            }
            
            return null;
        }
        
        /**
         * The heuristic algorithm to calculate the cost from a
         * <code>start</code> {@link Node} to the <code>end</code> {@link Node}.
         * <br>The Manhatten distance is used for this calculation to create
         * an overestimated heuristics for better search algorithms.
         * 
         * @param start The coordinates of the starting {@link Node}.
         * @param end The coordinates of the ending {@link Node}.
         * @return The calculated cost to get from the <code>start</code> point to the <code>end</code> point.
         */
        private int computeHeuristic(BlockPos start, BlockPos end) {
            return Math.abs(start.getX() - end.getX()) + Math.abs(start.getY() - end.getY());
        }
        
        private void getAdjacentNodes(Node node, BlockPos end) {
            // Create the set containing all neighbouring blocks
            BlockPos[] neighbourBlocks = new BlockPos[] {
                    node.location.offset(EnumFacing.WEST),  // x+1, y+0
                    node.location.offset(EnumFacing.EAST),  // x-1, y+0
                    node.location.offset(EnumFacing.NORTH), // x+0, y+1
                    node.location.offset(EnumFacing.SOUTH)  // x+0, y-1
            };
            
            for(BlockPos neighbourBlock : neighbourBlocks) {
                Node neighbour = new Node(neighbourBlock, node);
                if(this.closedSet.contains(neighbour) || !this.passableBlocks.contains(neighbourBlock)) continue;
                if(!this.frontier.contains(neighbour)) this.frontier.add(neighbour);
                float distance = node.distance + this.computeHeuristic(neighbour.location, end);
                
                if(distance >= neighbour.distance)
                    continue;
                neighbour.setDistance(distance);
            }
        }
        
        /**
         * Represents the location ("node") of a graph within the A* algorithm.
         */
        public class Node {
            
            /**
             * The location of this node in the world.
             */
            private BlockPos location = null;
            
            /**
             * The previous node or the node we came from.
             */
            private Node parent = null;
            
            /**
             *  also w or d or l or length
             */
            private float cost = 0.f;
            /** 
             * also g or d or 'cost so far'
             */
            private float distance = 0.f;
            /**
             *  also f
             */
            private float priority = 0.f;
            
            /**
             * Default constructor.
             */
            public Node() {
                this.location = null;
                this.parent = null;
                this.cost = 0.f;
                this.distance = 0.f;
                this.priority = 0.f;
            }
            
            /**
             * Creates a {@link Node} at the given location.
             * 
             * @param pos The location of the {@link Node} in the world.
             */
            public Node(BlockPos pos) {
                this.location = pos;
            }
            
            /**
             * Creates a copy of the given {@link Node}.
             * 
             * @param node The {@link Node} to copy.
             */
            public Node(Node node) {
                this.location = node.location;
                this.parent = node.parent;
                this.cost = node.cost;
                this.distance = node.distance;
                this.priority = node.priority;
            }
            
            /**
             * Creates a new {@link Node}.
             * 
             * @param pos The location of the {@link Node} in the world.
             * @param parent The previous {@link Node}.
             */
            public Node(BlockPos pos, Node parent) {
                this(pos, parent, 0.f, 0.f);
            }
            
            /**
             * Creates a new {@link Node}.
             * 
             * @param pos The location of the {@link Node} in the world.
             * @param parent The previous {@link Node}.
             * @param cost The cost to "move" through this {@link Node}.
             * @param distance The cost of all {@link Node}s from the start point to this {@link Node}.
             */
            public Node(BlockPos pos, Node parent, float cost, float distance) {
                this.location = pos;
                this.parent = parent;
                this.setCost(cost);
                this.setDistance(distance);
                this.setPriority();
            }
            
            private void setCost(float cost) {
                this.cost = cost;
            }
            
            private void setDistance(float distance) {
                this.distance = distance;
            }
            
            private void setPriority() {
                this.priority = this.distance + this.cost;
            }
            
            // TODO: research on hashCode & implement this & equals
        }
    }

     

    Note: I know that the memory usage could be improved by just using one list, but I'm not sure about the impact in performance this change would mean nor how I could implement this with my PriorityQueue.

     

    Thx in advance.

    Bektor

  10. Hi,

     

    I've just finished my basic fluid tank and I want to have it work now as a multi-block with different priorities:

    Unbenannt.thumb.png.2e1e3272ec0250a09e4506d213fbb0e4.png

    The numbers represent the priorities. Multiply tanks with the same priority mean that those parts of the tank will be filled equaly at the same time.

    Only when all blocks with the priority 1 are completly full all blocks with the priority 2 will begin to fill.

     

    Currently I'm only building up the list of all surrounding blocks. Note: Improvements of the code (regarding look and performance) are highly welcome. ;)

        public void generateMultiblock() {
            this.findTanks();
        }
        
        public void disbandMultiblock() {
            
        }
        
        private void findTanks() {
            this.tempTank = new HashSet<>();
            
            ArrayList<BlockPos> adjacentTanks = null;
            ArrayList<BlockPos> currentTanks = new ArrayList<>();
            
            // add this to the list to initiate the search
            currentTanks.add(this.getPos());
            // searches around all blocks and all adjacent blocks
            do {
                for(BlockPos curTank : currentTanks) {
                    adjacentTanks = this.findAdjacentTanks(curTank);
                    for(BlockPos adTank : adjacentTanks) {
                        // add to list but make sure it's no duplicate
                        if(this.tempTank.add(adTank))
                            currentTanks.add(adTank);
                    }
                }
                currentTanks.clear();
            } while (currentTanks.size() > 0);
        }
        
        private ArrayList<BlockPos> findAdjacentTanks(BlockPos tank) {
            if(tank == null)
                return null;
            ArrayList<BlockPos> adjacentTanks = new ArrayList<>();
            
            for(EnumFacing face : EnumFacing.VALUES) {
                // find tile entity in offset direction
                BlockPos offset = tank.offset(face);
                TileEntity tile = this.getWorld().getTileEntity(offset);
                if(tile != null && tile instanceof TileEntityFluidTank) {
                    // check if the tile entity is already connected to another tank as it can't have two masters
                    if(!((TileEntityFluidTank)tile).isConnected())
                        adjacentTanks.add(offset);
                }
            }
            return adjacentTanks;
        }

     

    It should also be noted that my TileEntityFluidTank is currently emtpy except for the isConnected method which currently always returns false (functionality to return true when already connected to a master block isn't quite there yet)

    Only the master block contains fluid logic and can hold fluids! The other blocks hold the fluid just client side (in terms of rendering), meaning that they don't have one line and should not have one line of code to handle fluids except for a value used

    for rendering (like x amount of fluid stored which is only used to render the x amount of fluid inside the tank at the location).

     

    I'm just having problems on how to get a performant way to build up the whole priority list. 

     

    Thx in advance.

    Bektor

  11. Due to how old Minecraft and Forge 1.7.10 are is it no longer supported on this forum.

    I suggest you should update to a newer version. ;)

     

    Spoiler

    Note: Even if 1.7.10 would still be supported, no one could help you without you providing us a crash report.

     

  12. Just now, larsgerrits said:

    Look at the subclasses of FluidEvent. They are related to moving, filling, draining and spilling of tanks. Their Javadoc tells you exactly what they do.

     

    Yes. However, if you're not adding functionality other than what the default implementation offers, you should probably use the default implementation.

     

    That's how it was done before the capability system was around, and they probably were to lazy to update their code.

    Ok, thx. I guess I'm good to go then. ;)

  13. 1 minute ago, larsgerrits said:

    default implementation (FluidTank) will call events based

    Hm, I'm wondering for what these events might be.

     

    7 minutes ago, larsgerrits said:

    TileFluidHandler is an example implementation of the fluid capability.

     

    Ah, ok. I could just implement IFluidTank and IFluidHandler in a custom class and handle it from there on like a normal capability?, nice.

    Thought now I'm wondering why some mods have a IFluidTank implementation and implement IFluidHandler in the tile entity itself...

  14. 4 hours ago, Draco18s said:

    The standard for containers is to divide the current used capacity by the total capacity and floor to an integer.

    Ok, thx.

     

    8 hours ago, larsgerrits said:

    IFluidHandler can be anything that handles fluids (items, blocks, tileentities), and an IFluidTank is specifically designed for tanks in an inventory.

    So I have to use both? That would leave me with the question of how my IFluidHandler should interact with IFluidTank and my tile-entitiy and how that all should work with capabilities.

  15. 2 hours ago, The_Wabbit said:

    Just a stab (2nd question)...

    Not really helpful.

    Also the point why I came up with the second question is that I'm used to the system from the capabilities that there is one interface

    which you implement and you're done, but here I've got two different interfaces.

    Thus I'm not quite sure what's the difference between both of them (except for that the one is the capability interface and the other one seems just to be there...) nor do I know

    what the actual use case of the second one should be when there is already the capability interface.

  16. 45 minutes ago, XixiPlays said:

    Hmm. I hope it is, anyway, thanks for the support. I've started looking at the API and looks pretty straightforward. Does the Forge API support java 8?(or 9 for that matter)

    It requires Java 8 as Minecraft 1.12 does while support for Java 9 isn't quite there yet.

    Note: You can't set the workspace up with Java 9.

    See here and here.

    51 minutes ago, XixiPlays said:

    I have one more question. Is everything thread-safe?

    I guess this could help to answer your question. From what I know not much changed there, so it should still be relevant.

     

     

    • Like 1
  17. 6 hours ago, Ugdhar said:

    http://mcforge.readthedocs.io/en/latest/datastorage/capabilities/

    Without ever having made a fluid tank myself, I'm going to guess that you create your block/item and attach an IFluidHandler capability to it, which will provide methods to manage the input/output of fluid and the fluid storage.

     

    Make a go at it, post your code to github, and if you run into issues, come back posting the logs and link to code. :)

     

    Ok, thx. Should be pretty easy then with the availability of an capability for fluids.

    Thought a few more questions:

    • I want my tank to output data to the comperator. The problem here is: I've got totally no clue how to calculate the return value for getComparatorInputOverride.
    • What is the difference between IFluidHandler and IFluidTank?
  18. Hi,

     

    I'm wondering how I can create a fluid tank using Forge and what is required to do so.

     

    Thx in advance.

    Bektor

     

    EDIT:

    Some more questions:

    • I want my tank to output data to the comperator. The problem here is: I've got totally no clue how to calculate the return value for getComparatorInputOverride.
    • What is the difference between IFluidHandler and IFluidTank and what is the actual use case of them? (I'm used to work with stuff like the energy capabilities where there is only one interface for everything, so I'm a bit confused here why there are two and when I should use which one or should I use both (and how then?)?)
  19. On 10/30/2017 at 8:41 AM, Choonster said:

    Use BlockStateContainer.Builder to create the BlockStateContainer, you can call BlockStateContainer.Builder#add once for each array of properties

    Thx, didn't know there was such a method.

     

    Thought as can be seen in the image: There is also an connection on the wrong side (happens only for west and east sides).

    Unbenannt.thumb.png.22f419e976e60fae7fbd9d66f9ad8a02.png

    	protected boolean isValidConnection(IBlockAccess worldIn, BlockPos pos, IBlockState neighbourState, EnumFacing facing) {
            if(super.isValidConnection(worldIn, pos, neighbourState, facing))
                return true;
            
            TileEntity tile = worldIn.getTileEntity(pos.offset(facing));
            if(tile != null)
                return EnergyUtils.hasCapability(tile, facing);
            
            return false;
        }
        
        private boolean canConnectToMachine(final IBlockAccess worldIn, final IBlockState state, final BlockPos pos, EnumFacing facing) {
            final BlockPos neighborPos = pos.offset(facing);
            final IBlockState neighbourState = worldIn.getBlockState(neighborPos);
            
            return this.isValidConnection(worldIn, pos, neighbourState, facing) && !(neighbourState instanceof BlockCableConnector) && !(neighbourState instanceof BlockCable);
        }
        
        @Override
        public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos) {
            for(final EnumFacing facing : EnumFacing.VALUES)
                state = super.getActualState(state, worldIn, pos).withProperty(SPECIAL_PROPERTIES.get(facing.getIndex()), this.canConnectToMachine(worldIn, state, pos, facing));
            
            return state;
        }
        
        @Override
        protected BlockStateContainer createBlockState() {
            return new BlockStateContainer.Builder(this)
                    .add(CONNECTED_PROPERTIES.toArray(new IProperty[CONNECTED_PROPERTIES.size()]))
                    .add(SPECIAL_PROPERTIES.toArray(new IProperty[SPECIAL_PROPERTIES.size()]))
                    .build();
        }

     

    BlockCableConnector is this block while BlockCable is the block on the left side of the image. The SPECIAL_PROPERTIES adds this larger thing on the smaller middle part of the cable while the CONNECTED_PROPERTIES just adds the normal element to the side (which can be seen in there.)

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.