Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Improve importing functions shared between block programs #1635

Open
laurensvalk opened this issue May 10, 2024 · 4 comments
Open
Labels
enhancement New feature or request software: pybricks-blocks Issues with blocks and code generation

Comments

@laurensvalk
Copy link
Member

Is your feature request related to a problem? Please describe.
You can define functions in one block project and import them into another (just like Python), but there are some subtleties that make this nontrivial at the moment.

For the following, let's assume there is one "main.py" program and a "module.py" with useful functions.

Right now, if main does any multitasking (async), it assumes functions imported from module are async too. Likewise, if main is synchronous, it will just call imported functions and not await them.

The workaround for users to make such programs work together is to just make everything async. (This is trivially done by just adding another start block, but it is not intuitive)

Solving it through blocks
We could make the block interface aware of multiple files/workspaces, and take all called functions into account when generating the code, as if it was a single project. Or keep it more or less as is, but regenerate all programs to use async if at least one of them does.

Solving it with Python
The way blocks are integrated right now, the above approach is not really realistic, even if that is perhaps the "correct" approach. Another way to do it is to generate some extra Python code when an imported block is called.

If the currently open main block project is synchronous, it could call imported functions as follows:

from module import imported_function

# utility function, only included if user imports are used
def make_sync(called_func):
    if called_func is a generator object:
        return run_task(called_func)
    return called_func

# currently the imported function caller block always gives:
imported_function(args)

# but we could generate:
make_sync(imported_function(args))

Likewise, if the main program uses multitasking, it could do:

async def the_main_start():
    # builtin function calls remain unchanged
    await wait(1000)

    # currently the imported function caller block always gives:
    await imported_function(args)

    # but we could generate
    (await imported_function(args)) if is_generator(imported_function) else imported_function(args)

    # could also do the above in a utility

Additional context
Example use case: I made a generic balance calculation function with blocks (synchronous, just math), but would like it to work whether the user's main program uses multitasking or not. If the library used Python, then it can already be solved in

@laurensvalk laurensvalk added enhancement New feature or request software: pybricks-blocks Issues with blocks and code generation labels May 10, 2024
@laurensvalk
Copy link
Member Author

Note that this does not change anything in Python programs, just how we auto-generate code for imported functions in block programs.

@dlech
Copy link
Member

dlech commented May 10, 2024

How about always generating 2 .py files for every module, a non-async version and an async version?

The async version could just be the same module name with _async suffix. Then an async main would import module_async while a non async main would import module. This way the generated source code stays readable and the binary .mpy files stay smaller. The unused generated files would just be ignored because they aren't imported.

@laurensvalk
Copy link
Member Author

Good idea. We might still need some way to handle imports from pure python modules though (without auto generated async variant).

Maybe this issue should just wait until after the cleaned up pull request for block integration, so we can tell block programs and python scripts properly apart.

For python modules, the imports can just be as is, without any special wrapping.

@laurensvalk
Copy link
Member Author

laurensvalk commented Jun 5, 2024

We might still need some way to handle imports from pure python modules though (without auto generated async variant)

Another solution which covers this but maybe also all the cases above, is to add a (optional) dropdown to the import block below, as e.g. below. So the block visually reads from module_name import [function ▼] function_name.

The dropdown could include (non-technical names of):

  • def
  • async def
  • constant or object (related to a request by @dlech a while ago)

Then it can generate the code correctly. For example, if a constant/object is imported, then the generated caller code would just be name instead of name().


So in the case below, instead of changing the Python utility to an async def as a workaround just because the calling block program uses multitasking, you could say from color_tools import [function ▼] get_hue, and the block program would know that it shouldn't add await when calling it.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request software: pybricks-blocks Issues with blocks and code generation
Projects
None yet
Development

No branches or pull requests

2 participants