Old 09-10-2019, 04:44 PM   #1
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default Require Can't Find Lua Module

I'm working on a couple of scripts that will use a common set of Lua functions. I've placed the functions in a separate Lua file and want to load it with a "require". My calling script can't seem to find the module even though it's in the same "Scripts" directory in the Reaper resources path.

Any guidance would be much appreciated.

Thanks,
Ken

ATOM.lua
Code:
local ATOM = { }

function ATOM.debug(message)
  reaper.ShowConsoleMsg(message .. "\n")
end

return ATOM
ATOM_test.lua
Code:
target = require ("ATOM")

target.debug("This is a test")
kenm is offline   Reply With Quote
Old 09-10-2019, 06:41 PM   #2
cfillion
Human being with feelings
 
cfillion's Avatar
 
Join Date: May 2015
Location: Québec, Canada
Posts: 2,725
Default

Use dofile with an absolute path or add the directory to package.path.

Code:
local path = ({reaper.get_action_context()})[2]:match('^.+[\\//]')
dofile(path .. 'ATOM.lua')
cfillion is online now   Reply With Quote
Old 09-10-2019, 08:38 PM   #3
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,140
Default

Depending on what you're doing, just be aware that dofile will load a fresh copy of the file each time you call it, whereas require reuses the same one so you can have the module caching things, etc.
Lokasenna is offline   Reply With Quote
Old 09-11-2019, 08:05 AM   #4
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Quote:
Originally Posted by cfillion View Post
Use dofile with an absolute path or add the directory to package.path.

Code:
local path = ({reaper.get_action_context()})[2]:match('^.+[\\//]')
dofile(path .. 'ATOM.lua')
Thanks @cfillion. I need to research the difference between dofile and require to understand which way to go.

Ken

Last edited by kenm; 09-11-2019 at 08:18 AM.
kenm is offline   Reply With Quote
Old 09-11-2019, 08:16 AM   #5
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Quote:
Originally Posted by Lokasenna View Post
Depending on what you're doing, just be aware that dofile will load a fresh copy of the file each time you call it, whereas require reuses the same one so you can have the module caching things, etc.
Thanks @Lokesenna. Good to know. When you say "fresh copy" does that mean it replaces the current copy every time you call it or it loads an additional copy? Following the DRY principle, my use case is to create a library of commonly used functions that interact with a Presonus ATOM Pad Controller. It's just a proof-of-concept at this point since I'm just starting to explore how the controller works in Native Control mode. Later on, I hope to build a control surface plugin for it.

Thanks again,
Ken
kenm is offline   Reply With Quote
Old 09-11-2019, 08:27 AM   #6
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,140
Default

It would load and run a separate copy of the file. The most obvious difference would something that manages a finite resource, like Reaper's graphics buffers (a script gets 1024 to work with).

Here's a very basic example of what my GUI uses - it just keeps a table of 3 = true, etc. so it knows which graphics buffers have been assigned.
Code:
local usedBuffers = {}

local bufferFunctions = {}
bufferFunctions.get = function()
  local n = #usedBuffers + 1
  usedBuffers[n] = true

  return #usedBuffers + 1
end

bufferFunctions.release = function(n)
  usedBuffers[n] = false
end

return bufferFunctions
If you had multiple modules loading that - think of the different elements in one of my GUI scripts - then dofile won't work:
Code:
-- Module A
local bufferAllocator = dofile("path/to/bufferAllocator.lua")
local myBuffer = bufferAllocator.get()
--> 1

-- Module B
local bufferAllocator = dofile("path/to/bufferAllocator.lua")
local myBuffer = bufferAllocator.get()
--> 1
Clearly there's a problem - both modules will be drawing on top of each other's work. With require, one copy of the module is reused and we get the correct behavior:
Code:
-- Module A
local bufferAllocator = require("path.to.bufferAllocator")
local myBuffer = bufferAllocator.get()
--> 1

-- Module B
local bufferAllocator = require("path.to.bufferAllocator")
local myBuffer = bufferAllocator.get()
--> 2
Lokasenna is offline   Reply With Quote
Old 09-11-2019, 12:03 PM   #7
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Thank you both for your help.

I've now got "require" working by setting the package.path. May I ask why Reaper has such a strange default package.path that points outside of Reaper?

Code:
reaper.ShowConsoleMsg("DEBUG: " .. package.path .. "\n")

/usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua
I would have expected it to be pointing to the Scripts directory in the Reaper resources path.

In any case, here's the working code that may help someone else who stumbles upon this thread.

ATOM.lua

Code:
local ATOM = { }

function ATOM.debug(message)
  reaper.ShowConsoleMsg(message .. "\n")
end

return ATOM
ATOM_test.lua

Code:
local path = ({reaper.get_action_context()})[2]:match('^.+[\\//]')
package.path = path .. "?.lua"

target = require("ATOM")

target.debug("\nThis message is coming from a module function\n")
Thanks again @Lokasenna for the example. It helps a lot.

Ken
kenm is offline   Reply With Quote
Old 09-11-2019, 12:13 PM   #8
mespotine
Human being with feelings
 
mespotine's Avatar
 
Join Date: May 2017
Location: Leipzig, Germany
Posts: 1,486
Default

I never use require if I can avoid it, because the default-searchpaths in Reaper are different on Windows and Mac.
One of the reasons, why one needs to load my Ultraschall-API using loadfile() and not require(), though I would have preferred require for less typing.

If you use loadfile only once in a script, it should mostly behave like require, with the benefit of having control over the location of the lua-file you want to load.

@lokasenna
Does require mean, it will load the required module just once per Reaper-instance or once per script?
__________________
Ultraschall-API - a Lua-functions-library4Reaper: https://forum.cockos.com/showthread....98#post2067798
Reaper Internals - Developerdocs4Reaper: https://forum.cockos.com/showthread.php?t=207635
mespotine is offline   Reply With Quote
Old 09-11-2019, 12:28 PM   #9
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,140
Default

Quote:
Originally Posted by mespotine View Post
@lokasenna
Does require mean, it will load the required module just once per Reaper-instance or once per script?
The latter; each script gets its own Lua environment.
Lokasenna is offline   Reply With Quote
Old 09-11-2019, 01:54 PM   #10
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Quote:
Originally Posted by mespotine View Post
I never use require if I can avoid it, because the default-searchpaths in Reaper are different on Windows and Mac.
One of the reasons, why one needs to load my Ultraschall-API using loadfile() and not require(), though I would have preferred require for less typing.

If you use loadfile only once in a script, it should mostly behave like require, with the benefit of having control over the location of the lua-file you want to load.

@lokasenna
Does require mean, it will load the required module just once per Reaper-instance or once per script?
These two lines of code just blow away the (useless) default search path and instead point it to the same directory as the running script.

Code:
local path = ({reaper.get_action_context()})[2]:match('^.+[\\//]')
package.path = path .. "?.lua"
Seems to work on both Mac and Windows.

Ken
kenm is offline   Reply With Quote
Old 09-12-2019, 08:59 AM   #11
mespotine
Human being with feelings
 
mespotine's Avatar
 
Join Date: May 2017
Location: Leipzig, Germany
Posts: 1,486
Default

Yes, but I didn't want to add my API-extension into the scripts-folder, rather into UserPlugins, where extensions belong to.
And scripts, who use extensions don't belong into UserPlugins but rather into Scripts.

So getting the path of the current script wouldn't work in my case as Ultraschall-API path isn't the scriptpath.

But for scripts in the same folder of the running script, your code-snippet would be helpful
__________________
Ultraschall-API - a Lua-functions-library4Reaper: https://forum.cockos.com/showthread....98#post2067798
Reaper Internals - Developerdocs4Reaper: https://forum.cockos.com/showthread.php?t=207635
mespotine is offline   Reply With Quote
Old 09-12-2019, 09:13 AM   #12
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,140
Default

This is why my GUI has an install script. I'm waiting for the day that a) ReaPack can run it automatically and b) Cockos add a preference to permanently edit your Lua path.
Lokasenna is offline   Reply With Quote
Old 09-12-2019, 09:32 AM   #13
mespotine
Human being with feelings
 
mespotine's Avatar
 
Join Date: May 2017
Location: Leipzig, Germany
Posts: 1,486
Default

I hope, that they'll add at some point startup-scripts in the UserPlugins-folder.
So you can have reaper_lokasenna_guilib.lua and it will be run at startup.
This would be awesome for everything that works as an extension, so housekeeping and checking could be done automatically without overcomplex constructs or installscripts who need to run by the user.

Just a regular restart, as every newly installed extension needs: Bamm, everything installed.

With that, I could add SWS and JS-Extension-installation into my US-API as well, so you just need to install one package for all extensions...
__________________
Ultraschall-API - a Lua-functions-library4Reaper: https://forum.cockos.com/showthread....98#post2067798
Reaper Internals - Developerdocs4Reaper: https://forum.cockos.com/showthread.php?t=207635
mespotine is offline   Reply With Quote
Old 09-12-2019, 12:25 PM   #14
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Quote:
Originally Posted by mespotine View Post
Yes, but I didn't want to add my API-extension into the scripts-folder, rather into UserPlugins, where extensions belong to.
And scripts, who use extensions don't belong into UserPlugins but rather into Scripts.

So getting the path of the current script wouldn't work in my case as Ultraschall-API path isn't the scriptpath.

But for scripts in the same folder of the running script, your code-snippet would be helpful
@mespotine,

It's funny you mention that. I wanted to segregate my scripts by putting them in a sub-folder under "Scripts" and that posed a problem since I needed to add the sub-folder to the package.path. The problem I had was the infamous "/" vs "\" separator issue.

As of Lua 5.2 this can be resolved with package.config.

Here's the code I came up with:

Code:
local config  = { }
local sep = ""
local atom_path = ""
local c = 0 

for s in package.config:gmatch("[^\n]+") do
    config[c] = s
    c = c + 1
end

sep = config[0] -- First element returned is the OS specific separator

atom_path = reaper.GetResourcePath() .. sep .. "Scripts" .. sep .. "ATOM" .. sep .. "?.lua"
reaper.ShowConsoleMsg(atom_path)

package.path = atom_path
Sorry this isn't the most elegant code. I haven't written much in Lua in the last 15 years.

You should be able to adopt this to adjust your package.path to add both "UserPlugins" as well as "Scripts" to your package search path and still be OS independent.
kenm is offline   Reply With Quote
Old 09-12-2019, 12:38 PM   #15
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,140
Default

Quote:
Originally Posted by kenm View Post
The problem I had was the infamous "/" vs "\" separator issue.
Windows, Mac, and Linux should all be happy if you use "/".

Last edited by Lokasenna; 09-12-2019 at 01:42 PM.
Lokasenna is offline   Reply With Quote
Old 09-12-2019, 01:51 PM   #16
kenm
Human being with feelings
 
Join Date: Dec 2011
Location: San Jose, CA, USA
Posts: 73
Default

Quote:
Originally Posted by Lokasenna View Post
Windows, Mac, and Linux should all be happy if you use "/".
Thanks. Good to know. I haven't done any programming in Windows since Windows 2000. These days it's pretty much Linux with a little bit of Mac sprinkled in here and there.

Ken
kenm is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 07:14 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.