cardillan / mindcode

A high level language for Mindustry Logic (mlog) and Mindustry Schematics.
http://mindcode.herokuapp.com/
MIT License
86 stars 13 forks source link

Contribution: Impact reactor logic #136

Open tom-leys opened 2 months ago

tom-leys commented 2 months ago

Hi. I wanted to contribute a working example if you wanted to play with the optimization of someone else's big code.

image

My current code for this is as follows

#set passes = 5 // Many passes to unroll and tidy this loop.

FUEL = @blast-compound
FUEL_OFF = @scrap
MIXER_IN = @pyratite

PWR_REQ = 50000
FLOW_REQ = 2000
PWR_ABORT = 10000
FLOW_ABORT = -500
FUEL_REQ = 10        // In generator
FUEL_TOT_REQ = 45    // In box (45 is enough for 60 seconds of running)
FUEL_LIMIT_LOW = 150     // Max fuel when no reactors running
FUEL_LIMIT = 800         // Max fuel generally
FUEL_TOT_ABORT = 15 // 20 seconds supply per reactor. Turn off reactors 2..5 as we start to run low for graceful 
                         // power reduction (better than all at once)
CRYO_REQ = 3 // This is a count out of 30 (3 is 10% full)
CRYO_REQ_EA = 2 // Each reactor needs a little more to start.
CRYO_ABORT = 1 // Note that this drastic low amount is balanced by many tanks around the generator
// NEGATIVE_TICKS = 1800 // 30 seconds (60 ticks times 30 seconds)
//                       // Rough time to become net-positive.
// STARTUP_TICKS = 3600 // 30 seconds (60 ticks times 30 seconds)
//                      // Rough time to be a serious generator

// Feed (IN) or empty (OUT) various reactors 
ULD_IN_1 = unloader1
ULD_OUT_1 = unloader2
ULD_IN_2 = unloader3
ULD_OUT_2 = unloader4
ULD_IN_MANY = unloader5
ULD_OUT_MANY = unloader6

// Feed the mixers with Pyro so they can make blast
ULD_MIXER = unloader7

TOWER = cyclone1
CONTAINER = vault1
NODE = node1

for reactor_idx in 1..5 
    emerg_shutdown = check_radar(TOWER)

    reactor = case reactor_idx
        when 1
            fuel = fuel_1
            reactor1
        when 2
            fuel = fuel_2
            reactor2
        when 3
            fuel = fuel_3
            reactor3
        when 4
            fuel = fuel_4
            reactor4
        when 5
            fuel = fuel_5
            reactor5
    end

    fuel = start_reactor(reactor, reactor_idx, emerg_shutdown, fuel)

    case reactor_idx
        when 1
            fuel_1 = fuel
            ULD_IN_1.config = fuel_1?FUEL:FUEL_OFF
            ULD_OUT_1.config = !fuel_1?FUEL:FUEL_OFF
        when 2
            fuel_2 = fuel
            ULD_IN_2.config = fuel_2?FUEL:FUEL_OFF
            ULD_OUT_2.config = !fuel_2?FUEL:FUEL_OFF
        else
            case reactor_idx
                when 3
                    fuel_3 = fuel
                    reactor3
                when 4
                    fuel_4 = fuel
                    reactor4
                when 5
                    fuel_5 = fuel
                    reactor5
            end
            fuel_many = fuel_3 or fuel_4 or fuel_5
            ULD_IN_MANY.config = fuel_many?FUEL:FUEL_OFF
            ULD_OUT_MANY.config = !fuel_many?FUEL:FUEL_OFF
    end
end

feed_mixers = throttle_blast(fuel_1 or fuel_2 or fuel_many, emerg_shutdown)
ULD_MIXER.config = feed_mixers?MIXER_IN:FUEL_OFF

idle_diff_generators()
printflush(message1)

def start_reactor(reactor, reactor_num, emerg_shutdown, fuel_on)
    fuel = reactor.blast-compound
    supply_fuel = true
    fuel_stored = CONTAINER.blast-compound
    cryo = reactor.cryofluid
    charge = floor(NODE.powerNetStored)
    surplus = floor(NODE.powerNetIn - NODE.powerNetOut) // Watts of excess generation

    if emerg_shutdown
        reactor.enabled = false
        printf("Reactor $reactor_num in emergency shutdown mode\n")
        supply_fuel = false
    elsif reactor.enabled 
        if charge < PWR_ABORT and surplus < FLOW_ABORT
            reactor.enabled = false
            printf("Reactor $reactor_num startup fail - battery low\n")
            supply_fuel = false
        elsif fuel < 1 
            // Not enough fuel to run
            reactor.enabled = false
            printf("Reactor $reactor_num shutdown - fuel low\n")
        elsif reactor_num > 1 and fuel_stored < FUEL_TOT_ABORT * reactor_num
            reactor.enabled = false
            printf("Reactor $reactor_num shutdown - system fuel low\n")
        elsif cryo <= CRYO_ABORT
            reactor.enabled = false
            printf("Reactor $reactor_num shutdown - out of cryo\n")
        else
            // Running, generating power        
            printf("Reactor $reactor_num running\n\n")
        end
    else 
        // Not running
        charge_units = charge / PWR_REQ / reactor_num
        flow_units = surplus / FLOW_REQ
        cryo_req = CRYO_REQ + CRYO_REQ_EA * reactor_num
        if flow_units + charge_units < 1
            printf("Reactor $reactor_num - insufficient power to start.\n")
            supply_fuel = false
        elsif fuel_on and fuel < FUEL_REQ
            if fuel + fuel_stored > FUEL_REQ
                // Start or continue fueling (note fuel on conveyor is not counted)
                printf("Reactor $reactor_num - loading startup fuel\n")
            else 
                printf("Reactor $reactor_num - insufficent system fuel to start\n")
            end
        elsif fuel_stored < FUEL_TOT_REQ * reactor_num
            // Keep waiting until enough fuel accumulates
            printf("Reactor $reactor_num - waiting for buffer fuel\n")
        elsif cryo < cryo_req
            // Keep waiting for cryo to be made
            printf("Reactor $reactor_num - need more cryo\n")
        else
            // Ready to startup!
            printf("Reactor $reactor_num - begin startup\n")
            reactor.enabled = true
        end        
    end
    return supply_fuel
end

def throttle_blast(reactors_fueled, emerg_shutdown)
    charge = floor(NODE.powerNetStored)
    surplus = floor(NODE.powerNetIn - NODE.powerNetOut) // Watts of excess generation
    fuel_stored = CONTAINER.blast-compound
    if (charge < PWR_ABORT and surplus < FLOW_ABORT) or emerg_shutdown
        limit = 0 // Don't produce any
        printf("Central Fuel at $fuel_stored / $limit - ")
        if emerg_shutdown 
            printf(" Enemies detected.\n")
        else
            printf(" Critical power.\n")
        end
    elsif !reactors_fueled
        limit = FUEL_LIMIT_LOW
        printf("Central Fuel at $fuel_stored / $limit - limited due to reactor shutdown.\n")
    else
        limit = FUEL_LIMIT
        printf("Central Fuel at $fuel_stored / $limit\n")
    end

    return fuel_stored < limit    
end

def check_radar(unit)
    enemy_found = unit.radar(enemy, any, any, health, 1)
    if enemy_found != null 
        projector1.enabled = true
        projector2.enabled = true

        // For now, panic when this happens (might get smarter later.)            
        // Make fuel flow out of reactors
        ULD_IN_1.config = FUEL_OFF
        ULD_OUT_1.config = FUEL
        ULD_IN_2.config = FUEL_OFF
        ULD_OUT_2.config = FUEL
        ULD_IN_MANY.config = FUEL_OFF
        ULD_OUT_MANY.config = FUEL
        ULD_MIXER.config = FUEL_OFF

        reactor1.enabled = false
        reactor2.enabled = false
        reactor3.enabled = false
        reactor4.enabled = false
        reactor5.enabled = false
        return true // Shutdown
    else
        projector1.enabled = false
        projector2.enabled = false
        return false // All clear
    end
end

def idle_diff_generators()
    stored_pc = NODE.powerNetStored / NODE.powerNetCapacity
    if stored_pc > 0.95 and reactor1.enabled and reactor2.enabled and reactor3.enabled and reactor4.enabled and reactor5.enabled
        // We don't need the differential reactors now
        printf("Differential reactors now idle (sufficient power).\n")
        generator1.enabled = false
        generator2.enabled = false
    else 
        generator1.enabled = true
        generator2.enabled = true
    end
end

Schematic

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
tom-leys commented 2 months ago

FYI I iterated on this a bit and this is my current version

#set passes = 5 // Many passes to unroll and tidy this loop.

printflush(message1)

FUEL = @blast-compound
FUEL_OFF = @scrap
MIXER_IN = @pyratite

PWR_REQ = 23000
FLOW_REQ = 2000
PWR_ABANDON = 2000   // Below this, assume something is horribly wrong and always shut down.
PWR_ABORT = 5000
PWR_RESERVE = 0.6    // Only plan on using 60% of battery capacity currently available.
FLOW_ABORT = -500
FUEL_REQ = 10        // In generator
FUEL_TOT_REQ = 45    // In box (45 is enough for 60 seconds of running)
FUEL_LIMIT_LOW = 150     // Max fuel when no reactors running
FUEL_LIMIT = 800         // Max fuel generally
FUEL_TOT_ABORT = 15 // 20 seconds supply per reactor. Turn off reactors 2..5 as we start to run low for graceful 
                         // power reduction (better than all at once)
GENERATOR_OUTPUT = 6500
GOAL_BUFFER = 10000              
CRYO_REQ = 3 // This is a count out of 30 (3 is 10% full)
CRYO_REQ_EA = 2 // Each reactor needs a little more to start.
CRYO_ABORT = 1 // Note that this drastic low amount is balanced by many tanks around the generator
// NEGATIVE_TICKS = 1800 // 30 seconds (60 ticks times 30 seconds)
//                       // Rough time to become net-positive.
// STARTUP_TICKS = 3600 // 30 seconds (60 ticks times 30 seconds)
//                      // Rough time to be a serious generator
fuel_1 = false
fuel_2 = false 
fuel_3 = false
fuel_4 = false
fuel_5 = false

surplus = floor(node1.powerNetIn - node1.powerNetOut) // Watts of excess generation

start_time = if surplus > 10000 
    // assume generators running OK
    @tick - 3600
else 
    @tick
end

t_startup1 = start_time
t_startup2 = start_time
t_startup3 = start_time
t_startup4 = start_time
t_startup5 = start_time

RET_T_STARTUP = t_startup1
REACTORS_DESIRED = 5 // Updates based on power fluctuations
AVG_SURPLUS = 500

while true
    // Feed (IN) or empty (OUT) various reactors 
    ULD_IN_1 = unloader1
    ULD_OUT_1 = unloader2
    ULD_IN_2 = unloader3
    ULD_OUT_2 = unloader4
    ULD_IN_MANY = unloader5
    ULD_OUT_MANY = unloader6

    // Feed the mixers with Pyro so they can make blast
    ULD_MIXER = unloader7

    TOWER = cyclone1
    CONTAINER = vault1
    NODE = node1

    emerg_shutdown = check_radar(TOWER)
    emerg_shutdown |= check_manual_shutdown()

    // Accumulate how much power is still required to start reactors
    START_POWER_REQ = calc_charge_req(reactor1, t_startup1)
    START_POWER_REQ += calc_charge_req(reactor2, t_startup2)
    START_POWER_REQ += calc_charge_req(reactor3, t_startup3)
    START_POWER_REQ += calc_charge_req(reactor4, t_startup4)
    START_POWER_REQ += calc_charge_req(reactor5, t_startup5)
    // Potentially code here to find last reactor to start up and shut it back down.

    fuel_1 = start_reactor(reactor1, 1, emerg_shutdown, fuel_1, t_startup1)
    t_startup1 = RET_T_STARTUP
    update_fuel(fuel_1, ULD_IN_1, ULD_OUT_1)

    fuel_2 = start_reactor(reactor2, 2, emerg_shutdown, fuel_2, t_startup2)
    t_startup2 = RET_T_STARTUP
    update_fuel(fuel_2, ULD_IN_2, ULD_OUT_2)

    emerg_shutdown |= check_radar(TOWER)
    fuel_3 = start_reactor(reactor3, 3, emerg_shutdown, fuel_3, t_startup3)
    t_startup3 = RET_T_STARTUP
    fuel_4 = start_reactor(reactor4, 4, emerg_shutdown, fuel_4, t_startup4)
    t_startup4 = RET_T_STARTUP
    fuel_5 = start_reactor(reactor5, 5, emerg_shutdown, fuel_5, t_startup5)
    t_startup5 = RET_T_STARTUP
    fuel_many = fuel_3 or fuel_4 or fuel_5
    update_fuel(fuel_many, ULD_IN_MANY, ULD_OUT_MANY)

    feed_mixers = throttle_blast(fuel_1 or fuel_2 or fuel_many, emerg_shutdown)
    ULD_MIXER.config = feed_mixers?MIXER_IN:FUEL_OFF

    diff_generators()
    surplus = kilo(AVG_SURPLUS)
    charge = kilo(NODE.powerNetStored)
    if START_POWER_REQ > 0 
        req = kilo(START_POWER_REQ)
        printf("Req ${req}kJ / ${charge}kJ. ${surplus}kW flow.\n")
    else
        printf("Power ${surplus}kW, ${charge}kJ\n")
        update_reactors_desired()
    end
    if REACTORS_DESIRED != 5
        printf("Require only ${REACTORS_DESIRED}/5 reactors.\n")
    end
    printflush(message1)
end

def kilo(val)
    return (val \ 100) / 10
end

def update_fuel(fuel, uld_in, uld_out)
    uld_in.config = fuel?FUEL:FUEL_OFF
    uld_out.config = !fuel?FUEL:FUEL_OFF
end

def calc_charge_req(reactor, time)
    secs = (@tick - time) \ 60
    if secs > 30 or !reactor.enabled
        return 0
    end

    time_left = 30 - secs            
    // Estimate of time left is 30 - secs
    // Linear estimate of impact reactor power draw is (1500 / 30 * time_left) * (0.5 * time left)
    // that simplifes to:
    charge_req = floor(time_left * time_left * 25)
    return charge_req
end

def start_reactor(reactor, reactor_num, emerg_shutdown, fuel_on, t_startup)
    RET_T_STARTUP = t_startup // return startup time unchanged by default
    secs = (@tick - t_startup) \ 60
    fuel = reactor.blast-compound
    supply_fuel = true
    fuel_stored = CONTAINER.blast-compound
    cryo = reactor.cryofluid
    charge = floor(NODE.powerNetStored)
    surplus = floor(NODE.powerNetIn - NODE.powerNetOut) // Watts of excess generation
    AVG_SURPLUS = AVG_SURPLUS * 0.8 + surplus * 0.2
    enabled = reactor.enabled

    printf("#$reactor_num - ")
    if emerg_shutdown
        reactor.enabled = false
        printf("emergency mode\n")
        supply_fuel = false
    elsif enabled 
        if REACTORS_DESIRED < reactor_num
            reactor.enabled = false
            printf("STOP unwanted\n")
        elsif reactor_num > 1 and fuel_stored < FUEL_TOT_ABORT * reactor_num
            reactor.enabled = false
            printf("STOP system fuel low\n")
        elsif fuel <= 1 
            // Not enough fuel to run
            reactor.enabled = false
            printf("STOP fuel low\n")
        elsif cryo <= CRYO_ABORT
            reactor.enabled = false
            printf("STOP out of cryo\n")
        elsif secs < 30
            // Reactor still in startup
            charge_req = calc_charge_req(reactor, t_startup)

            if charge - charge_req < PWR_ABORT and surplus < FLOW_ABORT
                reactor.enabled = false
                printf("START FAIL battery\n")
                supply_fuel = false
            else 
                time_left = 30 - secs            
                printf("Starting $time_left secs, req ${charge_req}J\n")
            end
        elsif charge < PWR_ABANDON and surplus < FLOW_ABORT
            reactor.enabled = false
            printf("ABANDON - battery crit\n")
            supply_fuel = false
        else
            // Running, generating power        
            // Round to 1 dp
            mins = secs \ 6
            mins = mins / 10
            printf("RUN $mins mins\n")
        end
    else 
        // Not running
        // 1 charge_units of charge if there is enough in battery to start other generators + 
        charge_units = (charge - START_POWER_REQ - PWR_ABORT) / PWR_REQ * PWR_RESERVE
        flow_units = max(-0.1, surplus / FLOW_REQ)
        cryo_req = CRYO_REQ + CRYO_REQ_EA * reactor_num

        if REACTORS_DESIRED < reactor_num
            printf("IDLE\n")
            supply_fuel = false
        elsif flow_units + charge_units < 1
            flow = floor(flow_units * 1000) / 10
            charge = floor(charge_units * 1000) / 10
            printf("WAIT power. (%${charge}J + %${flow}W)\n")
            supply_fuel = false
        elsif fuel_on and fuel < FUEL_REQ
            if fuel + fuel_stored > FUEL_REQ
                // Start or continue fueling (note fuel on conveyor is not counted)
                printf("WAIT fuel\n")
            else 
                printf("WAIT system fuel\n")
            end
        elsif fuel_stored < FUEL_TOT_REQ * reactor_num
            // Keep waiting until enough fuel accumulates
            printf("WAIT buffer fuel\n")
        elsif cryo < cryo_req
            // Keep waiting for cryo to be made
            printf("WAIT cryo\n")
        else
            // Ready to startup!
            printf("STARTUP\n")
            START_POWER_REQ += PWR_REQ
            reactor.enabled = true
        end        
    end
    if enabled != reactor.enabled
        // Reactor changed state.
        RET_T_STARTUP = @tick
    end
    return supply_fuel
end

def throttle_blast(reactors_fueled, emerg_shutdown)
    charge = floor(NODE.powerNetStored)
    surplus = floor(NODE.powerNetIn - NODE.powerNetOut) // Watts of excess generation
    fuel_stored = CONTAINER.blast-compound
    if (charge < PWR_ABORT and surplus < FLOW_ABORT) or emerg_shutdown
        limit = 0 // Don't produce any
        printf("Central Fuel at $fuel_stored / $limit - ")
        if emerg_shutdown 
            printf(" Enemies detected.\n")
        else
            printf(" Critical power.\n")
        end
    elsif !reactors_fueled
        limit = FUEL_LIMIT_LOW
        printf("Fuel $fuel_stored / $limit (due to emergency)\n")
    else
        limit = FUEL_LIMIT
        printf("Fuel $fuel_stored / $limit\n")
    end

    return fuel_stored < limit    
end

def check_radar(unit)
    enemy_found = unit.radar(enemy, any, any, health, 1)
    if enemy_found != null 
        projector1.enabled = true
        projector2.enabled = true

        // For now, panic when this happens (might get smarter later.)            
        // Make fuel flow out of reactors
        ULD_IN_1.config = FUEL_OFF
        ULD_OUT_1.config = FUEL
        ULD_IN_2.config = FUEL_OFF
        ULD_OUT_2.config = FUEL
        ULD_IN_MANY.config = FUEL_OFF
        ULD_OUT_MANY.config = FUEL
        ULD_MIXER.config = FUEL_OFF

        reactor1.enabled = false
        reactor2.enabled = false
        reactor3.enabled = false
        reactor4.enabled = false
        reactor5.enabled = false

        return true // Shutdown
    else
        projector1.enabled = false
        projector2.enabled = false
        return false // All clear
    end
end

def check_manual_shutdown()
    if switch1 != null and !switch1.enabled
        println("User has disabled emergency switch")
        return true // user can force a shutdown
    end
    return false
end

def diff_generators()
    stored_pc = NODE.powerNetStored / NODE.powerNetCapacity
    if stored_pc > 0.90 and START_POWER_REQ < 10
        // We don't need the differential reactors now
        printf("Differential now idle.\n")
        generator1.enabled = false
        generator2.enabled = false
    else 
        generator1.enabled = true
        generator2.enabled = true
    end
end

def update_reactors_desired()
    stored = NODE.powerNetStored / NODE.powerNetCapacity
    if stored > 0.98 and AVG_SURPLUS > GENERATOR_OUTPUT + GOAL_BUFFER
        if REACTORS_DESIRED > 2
            REACTORS_DESIRED -= 1
            AVG_SURPLUS -= GENERATOR_OUTPUT
        end
    end

    if stored < 0.80 and AVG_SURPLUS < GOAL_BUFFER * 0.8
        needed = max(1, (AVG_SURPLUS - GOAL_BUFFER) \ GENERATOR_OUTPUT)

        REACTORS_DESIRED = min(5, needed + REACTORS_DESIRED)
    end
end
cardillan commented 2 months ago

Thank you a lot!

Suggestion: you can add #set instruction-limit = 1050 at the beginning of the program. Here's an explanation from the documentation:

This option allows to change the instruction limit used by speed optimization. The speed optimization strives not to exceed this instruction limit. In some cases, the optimization cost estimates are too conservative - some optimizations applied together may lead to code reductions that are not known to individual optimizers considering each optimization in isolation. In these cases, increasing the instruction limit might allow more optimizations to be performed. When the resulting code exceeds 1000 instructions, it is not usable in Mindustry processors and the option should be decreased or set back to 1000.

In this case, the optimizer is able to inline one more function and the resulting code size is 982 instructions.

tom-leys commented 2 months ago

Glad it is helpful!

I'm not always sure that the size vs speed trade off is worth it. If the code goes "fast enough" then increasing instruction count makes the code harder for non-mindcode users to read and also makes the schematic string longer. I love how the function inlining lets the compiler bake in things like "this is reactor_1" but it does come at a bit of a cost.