Search code examples
pythonminecraft

Creating a Global Instance of a Class Inside of a Function


So I'm writing a filter for the Minecraft terrain manipulation program MCEdit. The filter is written in Python 2 (That's the only thing MCEdit will read). MCEdit calls the filter by passing the variables level (Of type MCLevel), box and options to the user defined function perform(level, box, options). You can find the documentation on this here >> https://github.com/mcedit/pymclevel

I have written multiple filters before, so I know the general idea of how it works. Here is my code:

from pymclevel import *
import random

inputs = (("Replace", (0, 1, 255)), ("Of Damage", (0, 0, 15)), ("With", (1, 1, 255)), ("Of Damage ", (0, 0, 15)), ("This Percent of the Time", (50, 0, 100)), ("If it is Beside", (False)), ("Or Diagonal", (False)), ("Exclusively", (False)), ("To", (0, 1, 255)), ("Of Damage  ", (0, 0, 15)))

def getBlock(x, y, z):
    global level
    return (level.blockAt(x, y, z), level.blockDataAt(x, y, z))

def setBlock(x, y, z, block, data):
    global level
    level.setBlockAt(x, y, z, block)
    level.setBlockDataAt(x, y, z, data)

def IsBeside(x, y, z, block, data):
    return (getBlock(x - 1, y, z) == (block, data) or getBlock(x + 1, y, z) == (block, data) or getBlock(x, y - 1, z) == (block, data) or getBlock(x, y + 1, z) == (block, data) or getBlock(x, y, z - 1) == (block, data) or getBlock(x, y, z + 1) == (block, data))

def IsDiagonal(x, y, z, block, data):
    return (getBlock(x - 1, y - 1, z) == (block, data) or getBlock(x - 1, y, z - 1) == (block, data) or getBlock(x, y - 1, z - 1) == (block, data) or getBlock(x + 1, y + 1, z) == (block, data) or getBlock(x + 1, y, z + 1) == (block, data) or getBlock(x, y + 1, z + 1) == (block, data) or getBlock(x + 1, y + 1, z + 1) == (block, data) or getBlock(x - 1, y + 1, z + 1) == (block, data) or getBlock(x + 1, y - 1, z + 1) == (block, data) or getBlock(x + 1, y + 1, z - 1) == (block, data) or getBlock(x - 1, y - 1, z + 1) == (block, data) or getBlock(x - 1, y + 1, z - 1) == (block, data) or getBlock(x + 1, y - 1, z - 1) == (block, data) or getBlock(x - 1, y - 1, z - 1) == (block, data))

def perform(level, box, options):

    Replace = options["Replace"]
    ReplaceData = options["Of Damage"]
    With = options["With"]
    WithData = options["Of Damage "]
    Percent = options["This Percent of the Time"]
    Beside = options["If it is Beside"]
    Diagonal = options["Or Diagonal"]
    Exclusive = options["Exclusively"]
    Check = options["To"]
    CheckData = options["Of Damage  "]

    mark = False

    for x in xrange(box.minx, box.maxx + 1):
        for z in xrange(box.minz, box.maxz + 1):
            for y in xrange(box.miny, box.maxy + 1):
                if random.randint(1, 100) <= Percent and getBlock(x, y, z) == (Replace, ReplaceData):

                    if Beside and not Diagonal and not Exclusive:
                        if IsBeside(x, y, z, Check, CheckData):
                            mark = True

                    elif not Beside and Diagonal and not Exclusive:
                        if IsDiagonal(x, y, z, Check, CheckData):
                            mark = True

                    elif Beside and not Diagonal and Exclusive:
                        if IsBeside(x, y, z, Check, CheckData) and not IsDiagonal(x, y, z, Check, CheckData):
                            mark = True

                    elif not Beside and Diagonal and Exclusive:
                        if not IsBeside(x, y, z, Check, CheckData) and IsDiagonal(x, y, z, Check, CheckData):
                            mark = True

                    elif Beside and Diagonal:
                        if IsBeside(x, y, z, Check, CheckData) and IsDiagonal(x, y, z, Check, CheckData):
                            mark = True

                    else:
                        mark = True

                    if mark:
                        level.setBlock(x, y, z, With, WithData)
                        mark = False

My problem is that I need to call getBlock and setBlock an absurd number of times, and it's quite likely that level will contain HUGE amounts of information (Enough that it could take multiple seconds just to copy it into a function). By extension, that means the filter could easily run for hours, most of that time just spent copying level. Naturally, I don't want to do that, and since python has no pass by reference, and I can't access the original variable that was passed into perform, I can only try and access the instance of level passed to perform from the other functions. That's where the global stuff comes in, which obviously doesn't work. Does anyone know of a way to make this work without passing level as a function argument to getBlock and setBlock? I don't care if it uses global or not, that was just my thought.


Solution

  • Have you run this code and confirmed that it is actually very slow? Python doesn't use pass-by-value or pass-by-reference, rather it uses pass-by-assignment for parameter passing. It can be hard to grasp at first, but there are some important reasons for doing it this way. To the best of my understanding, passing in very large objects to functions in python is not as cumbersome as in say C/C++, because you aren't copying the object data from one memory location to another, rather the assignment data. I've often heard it called "pass-by-reference-by-value", meaning you are passing a reference by value. This is confusing, and I prefer this explanation: Code like a pythonista

    If you'd like to change the contents or value of the parameters in the callee, and have those changes be reflected in the outer scope, there are a number of ways to do it. If the parameters are always mutable (e.g. a list) then you can modify them in the callee no problem as long as your modifications are in-place e.g. no re-assignments. If they are immutable, your best bet is to just return a tuple of the changed objects. You can also have a look at the other techniques in the pass-by-assignment article (the same link as above).

    Hopefully I haven't confused you more. :) Good luck!

    P.S. If you have the time, give the rest of Code like a pythonista a read. It's a pretty quick read and full of tips that will make your python coding go more smoothly.