Using libraries - best practices

“With great power comes great responsibility.”

Now that we can import “libraries”, it strikes me that this is something where lots of people are going to do essentially equivalent things, and it might be useful to start off with some idea sharing (that will almost certainly lead to code sharing).

In particular, the first thing that I noticed was that all the tabs (except “Main”) in the dependency are loaded and are loaded before the files in the current project. Up to now, I’ve stuffed all my “library” files into a single project. I don’t always want to load the whole lot. But purely on an organisational level, I’m not sure that I want to put each “library” file into a single project so that I can import single files at will. So I’m thinking that my library files should not actually define anything, but define a single “load” function that when called will load the code. That way, every project can just load the “standard library” and then start with a load of “import X” commands which will actually load the code.

But I’m well aware that my programming abilities are limited and there may be Deep Problems with this. I’m also sure that others will have thought of this issue and have their own ideas on how best to organise and selectively import their library files. So before I attempt to code anything, I’d like to know if it is a reasonable idea and if anyone else has already done it so that I can simply steal their code.

Hi Andrew,

Not sure what you mean here, it sounds like you wish to have fixed libraries and textual calls for them in dedicated files.

I see several potential problems straight away with ‘libraries’. Everyone will have their own libraries and they will be modified, not documented and not obviously called from the users code.

My personal view is that libraries should be stored in code depositories like Bitbucket and GitHub, preferably with some textual instructions provided both in and out of code. That way users can all pull them down as needed - perhaps they need a unique ID to avoid confusion.

Libraries should be grouped on functionality - like graphics, 3D, sound UI etc.

I like the way that Love2D uses the required call for identified libraries(tabs). Perhaps this could be an option - when a library is identified a line is added to a ‘library’ section in main - a la:

Libraries:
      required("gfx/bezier")
      required("UI/textBox")
      required("games/joyPad")
  end

This should give some traceability.

You may have already implemented some of these - I haven’t, as yet, tried out this in 1.4.3.

Bri_G

:slight_smile:

I would actually advocate a 1-project-per-library model. For the following reasons:

  • Easier to share and update (especially with only copy+paste)
  • The “Main” tab in your library project can now become the example / tutorial about how to use that library
  • Easy to selectively include functionality in another project, ability to clearly see what functionality is included from the dependency list

I’m of the opposite opinion. I’d prefer to keep all my libraries in a single project. For the following reasons:

  • Easier to share and update (especially given I might want to import libraries from lots of people, so I can have a single project: “Bortel’s Files”, “Rui’s Files” etc, without having to remember that Bortel’s Font library is my BFont library to avoid name clashes).
  • Few of my libraries can have an example/tutorial without loading a fair amount of other code, so maintaining a “Main” for each one is a bit more work than I want to do.
  • Few of my libraries stand alone, most were developed as part of a larger project and depend on other libraries, so chasing the trail of dependencies is going to be tricky to keep track of. If they’re all in one place, it’s easy for one file to “import” another.

Fair enough. I guess the type of use will just emerge organically. Hopefully the feature is useful.

Oh absolutely! The point of this “discussion” was to ensure that we benefit from each other’s ideas to accelerate the evolution. As I said, I’m sure that there’s lots that I haven’t thought of. One big drawback with my plan is that to implement it, I suspect that I have to read all the files and then execute the ones I want. Your method avoids that.

What would make mine work a bit better would be if I could have a single file of the current project that was read before all the included files which could define some conditionals. Then even though it would still have to scan through all the included files, it would process them as it met them.

Hi, I haven’t tried yet the new library support because I’m using a custom lua sandbox and I’m far from a computer, but it’s a while I’m working using @ruilov hack for library import that, from what I’ve read, works in a similar way. My way to work til now was to create one project per each logical lib and having main tab as demo/test unit for each lib, as @Simeon suggested. The main problem with this approach is loosing the possibility to easy navigate through code, because it requires each time to change project ecc. A kind of solution would be having the search panel enumerate also function signature of other libs, maybe showing also comments if declared in a standard way…

I think it would be really great to have kind of subprojects, to obtain something like what @Bri_G suggests, but I fear it something that goes in another direction and, anyway, not so necessary (according to me)

Anyway: great new feature, thanks a lot @Simeon! After libs support there’s only one thing I really feel missing now: native support for load generic files and subdirs in own folders (spritepacks or dropbox). The pity is that with a custom lua sandbox is already possible to load, for example, an xml file from dropbox\mysubfolder and having that support really changed my way to use codea.

Thanks for all the feedback. Having the dependent projects index into autocomplete and search is a good idea.

@shrike file IO is unsandboxed in the latest version. The main reason for this is to let people load custom assets while I work on a more Codea-like implementation of this feature.

wow, what a great news!

I’m currently working loading everything I need from dropbox subfolders, that’s the code I’m using:


IO = {}

local __io__project = nil

function IO.setBaseDir(project)
    __io__project = project
end

function IO.getFile(fileName)
    local home = os.getenv("HOME")
    local dir = home.."/Documents/Dropbox.spritepack/"
    if __io__project then
        dir = dir ..__io__project.."/"
    end
    local _fileName = dir .. fileName
    local file,err = io.open(_fileName,"r")
    if not err then
        local s = file:read("*all")
        io.close(file)
        return s
    else
        return nil, err
    end
    
end

where setBaseDir is used to set the as working dir a specific subfolder of the dropbox synced folder. Is that (or something similar) possible with latest release?

Unfortunately not, because os.getenv is still sandboxed. I’ll have to consider allowing that one.

File.io is no longer sandboxed?! Awesome, that is the best undocumented addition ever. I must go and try this now. This whole update has been a great one IMO.

Hi @Simeon, can you explain better how File.IO can be used? Or better, what about the filesystem? where openfile(name) is going to look for ‘name’? And is it possible to move through folder? because if it’s ser eith Documents work dir, for example, and is it possible to move thorugh different codea folders the code above could be changed easily…

Actually, @shrike, I’m not too sure. I made it available because people were using it, anyway, and I thought it might help people while waiting for an official solution.

One of the reasons it was sandboxed in the first place was because of exactly the reasons you mention. Where do we look for files? What is the root folder? I didn’t like these issues as they didn’t seem to fit with the way Codea works. I have an API in mind that I would like to provide instead of the Lua io package, in a future update.

One downside of all projects in place, is the increased likelihood of name clashes. Also kind of annoying to have to browse a large number of tabs in the same project.

But, if project dependencies are not recursive, that’s what we’ll have to do. @Simeon, is hard to make them recursive?

Hi @ruilov, wait, what you mean with ‘not recursive’? It’s not possible to have project A depending on project B and proj B depending on proj C? tell me not…!

@Simeon I think that just exposing some macro as entry point for the filesystem, like mounting point (similar to what already happens for spritepacks) could be enough, and it could also maybe addressed in a more visual way (like images are now), that i suppose to be one of your concerns.
I expect things like ‘dropbox’, ‘documents’, ‘spritepackname’, ‘projectname’ or similar.

Another solution could be just having access to a common root and having all the previous as subfolder, that is something similar to what happens with
os.getenv(“HOME”)

With the code I posted before for example I’m used to create a subfolder in dropbox for each project I’m working on, so to have separated files per project and have easy access from my iPad. I can work to almost everything without having to connect to a computer, the only thing I need is a 3g or wifi internet connection. And it’s really great.

Anyway I’ll wait for the IO update that you have in mind, I’m sure it will be a very good solution like all the new feature you are adding to Codea. :slight_smile:

I’ve done a few little experiments and found that the order of loading of external libraries is the order in which you click the “checkmark”. So to do what I want - have all files in a single library project but only include a select number - I could use one of the following schemes:

  1. Load a “header” project first which has the “include” directives. This will have a single file which will define a set of conditionals as to which files from the Big Library to load. The files in the Big Library all start with something like if to.be.included then so that they are effectively ignored if not selected.

  2. Each file in the Big Library starts with something like include["this library"] = function () so that it stores the code but doesn’t run it. Then in my original project I run the relevant includes to load the code.

Both have the downside that all the files in the Big Library are actually read, but I don’t see a way around that with my all-in-one approach. The first has the advantage that any libraries not loaded are read-and-discarded with (presumably) minimal processing - all Lua needs to do is find the closing end of the conditional. However, I need to load an extra “header” project first, I can’t change the order of loading the files, and dependencies would be tricky: if a file depends on an earlier one it can’t force its being loaded. The second approach avoids all these disadvantages but does have the significant disadvantage that all the functions are defined (and therefore presumably incur some processing cost) and held in memory. I can always delete them from memory after they’ve been loaded (good idea to prevent double loading anyway), but it doesn’t save the initial cost.

In terms of memory/processing cost it comes down to the difference between:

if false then
    -- a load of stuff
end

and

function doNotCallMe()
    -- a load of stuff
end
doNotCallMe = nil

which I have no idea about.

@Andrew_Stacey - pardon my ignorance but are you trying to address a compile speed issue or reducing the size of the compiled file (or both)?

I’m not clear on what happens with functions in your dependent Library that aren’t used in the current program.

That’s what I’m focussing on. I want to have all my library files in a single “Project”. So when importing them, all files will be read. I want to minimise the impact - memory or speed - of the files that aren’t actually used.

Current thinking is that there’s no big difference between my two methods. So I’ll go for the function call as it gives me more flexibility.

So my initial code is as follows. I define Import.lua as:

-- Import code

import = {}
import.imported = {}
import.libraries = {}

import.finished = function ()
    import.libraries = {}
end

import.all = function ()
    for _,v in pairs(import.libraries) do
        v()
    end
    import.libraries = {}
end

import.library = function(f)
    if import.libraries[f] then
        import.libraries[f]()
        import.libraries[f] = nil
        table.insert(import.imported,f)
    end
end

and make sure that I load it first in the library “project”. Then each library file looks like:

import.libraries.<LibraryName> = function()
...
end

In my actual projects, I’ll put:

import.library("<LibraryName>")

to load them. I can also put that in the individual library files to ensure that dependencies are properly loaded.