Skip to content

Instantly share code, notes, and snippets.

@schorg
Last active April 1, 2020 20:45
Show Gist options
  • Save schorg/619cab9290ba0d23d0f1f0e4eb493848 to your computer and use it in GitHub Desktop.
Save schorg/619cab9290ba0d23d0f1f0e4eb493848 to your computer and use it in GitHub Desktop.
Sequentially concurrent composition.

The code and discussion is related to the Blech example blinker

The follwing code - which is part of the main activity in the original example - sequentialises the concurrent execution of two activities in order to change the output blinkerLeverPos.

Although it is perfectly correct it is quite complicated. The sequential data-flow has to be organised explicitly.

activity monitorBlinkerLever(rotation: int32, blinkerLeverMove: int32)(blinkerLeverPos: int32)
    var movedBlinkerLeverPos: int32
    var localBlinkerLeverPos = blinkerLeverPos
    
    cobegin
        run moveBlinkerLever(blinkerLeverMove, prev localBlinkerLeverPos)(movedBlinkerLeverPos)
    with
        run updateBlinkerLeverFromRotation(rotation, movedBlinkerLeverPos)(localBlinkerLeverPos)
    with
        repeat
            blinkerLeverPos = localBlinkerLeverPos
            await true
        end
    end
end

The need to change something sequentially in concurrent trails/threads is quite common. A control flow statement that allows sequentially concurrent execution could substiantially simplify the code.

The following code would begin with a step in the first activity and then execute a step in the second activity. That means it sequentially combines execution steps in each control flow.

activity monitorBlinkerLever (rotation: int32, blinkerLeverMove: int32)
                             (blinkerLeverPos: int32)
    begin
        run moveBlinkerLever(blinkerLeverMove)(blinkerLeverPos)
    then
        run updateBlinkerLeverFromRotation(rotation)(blinkerLeverPos)
    end
end

Note that this "folding" of execution steps is strictly sequential and does not need causality analysis. Furthermore the called sub-activities can be simplified too, because there is no need to explicitly organise the data flow. They just update the blinkerLeverPos if necessary.

activity moveBlinkerLever(blinkerLeverMove: int32)(blinkerLeverPos: int32)
    repeat
        if blinkerLeverMove == BLINKER_LEVER_MOVE_UP then
            blinkerLeverPos = cap(prevBlinkerLeverPos - 1, BLINKER_LEVER_POS_UP, BLINKER_LEVER_POS_DOWN)
        elseif blinkerLeverMove == BLINKER_LEVER_MOVE_DOWN then
            blinkerLeverPos = cap(prevBlinkerLeverPos + 1, BLINKER_LEVER_POS_UP, BLINKER_LEVER_POS_DOWN)
        end
        await true
    end
end


activity updateBlinkerLeverFromRotation(rotation: int32)(blinkerLeverPos: int32)
    var rotationSum: int32
    repeat
        if movedBlinkerLeverPos != BLINKER_LEVER_POS_CENTER then
            updateRotationSum(rotation)(rotationSum)

            if rotationSum <= -ROTATION_THRESHOLD 
               and movedBlinkerLeverPos == BLINKER_LEVER_POS_UP then
                blinkerLeverPos = BLINKER_LEVER_POS_CENTER
                rotationSum = 0
            elseif rotationSum >= ROTATION_THRESHOLD 
                   and movedBlinkerLeverPos == BLINKER_LEVER_POS_DOWN then
                blinkerLeverPos = BLINKER_LEVER_POS_CENTER
                rotationSum = 0
            end
        end
        await true
    end
end

Currently, we (the Blech team) are discussing the extension of the language with such a control flow statement.

    begin
        ... 
    then
        ...
    then     // more than one then possible
        ...
    end

It would terminate if one of the combined parts terminates in a step. Different to the cobegin the parts would not finish their steps.

The preemption (abort) would become syntactic sugar.

    when condition abort
        ... // executes in steps
    end
    // go on here if condition becomes true

could be expressed as

    begin
        await condition
    then
        ... // executes in steps
    end
    // go on here if condition becomes true

The latest extension of the example with a warning indicator could also be better seperated into an activity that controls the blinkers via the blinker lever and another activity that controls (overrides) the blinkers and the warning indicator when the warning button is pushed.

The extension of my branch with internal access to sensor and actuators could be structured as follows

@[EntryPoint]
singleton activity main()
    @[CInput (binding="sensor_input.blinker_warning_pushed", header="blinker_env.h")]
    extern let warningPushed: bool         
        
    @[COutput (binding="actuator_state.blinker_lever_pos", header="blinker_env.h")]
    extern var blinkerLeverPos: int32

    var rotation: int32
    cobegin     
        run monitorBlinkerLever(rotation)(blinkerLeverPos)
    with
        run monitorWheelPos()(rotation)
    with
        @[COutput (binding="actuator_state.blinker_left_on", header="blinker_env.h")]
        extern var leftBlinker: bool         
        @[COutput (binding="actuator_state.blinker_right_on", header="blinker_env.h")]
        extern var rightBlinker: bool         
        begin 
            run controlBlinkers(blinkerLeverPos)(leftBlinker, rightBlinker) 
        then
            // would update left and right blinker 
            // access to warningPushed and the warning indicator can be encapsulated.
            run controlWarningIndication()(leftBlinker, rightBlinker)  
        end
    end
end
@frameworklabs
Copy link

I agree that this addition would simplify this and similar tasks, so I would welcome it.

On the down side, maybe people might think too much in sequential data-flows again instead of leaving this to the compiler. Also, the language gets a bit more bloated by that.

Minor note: I feel that "cobegin" and "begin" are a bit too similar. Actually I could see "cobegin" to be changed to something like "concur".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment