Old 05-09-2019, 09:34 PM   #1
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,740
Default Lua code for quickly getting a track by GUID

I just wrote this, maybe someone will find it useful:
Code:
local track_guid_cache = {};

function track_from_guid_str(proj, g)
  local c = track_guid_cache[g];
  if c ~= nil and reaper.GetTrack(proj,c.idx) == c.ptr then
    -- cached!
    return c.ptr;
  end
  
  -- find guid in project
  local x = 0
  while true do
    local t = reaper.GetTrack(proj,x)
    if t == nil then
      -- not found in project, remove from cache and return error
      if c ~= nil then track_guid_cache[g] = nil end
      return nil
    end
    if g == reaper.GetTrackGUID(t) then
      -- found, add to cache
      track_guid_cache[g] = { idx = x, ptr = t }
      return t
    end
    x = x + 1
  end
end
Justin is online now   Reply With Quote
Old 05-09-2019, 11:22 PM   #2
Ivannn Bennnettt
Human being with feelings
 
Join Date: Feb 2017
Posts: 305
Default

Thank you Do you like write in Lua?
Ivannn Bennnettt is offline   Reply With Quote
Old 05-10-2019, 02:38 AM   #3
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 9,900
Default

Thx, but we already have



Code:
reaper.BR_GetMediaTrackByGUID( proj, guidStringIn )

in SWS extension :P
X-Raym is offline   Reply With Quote
Old 05-10-2019, 03:06 AM   #4
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,175
Default

Quote:
Originally Posted by X-Raym View Post
Thx, but we already have



Code:
reaper.BR_GetMediaTrackByGUID( proj, guidStringIn )

in SWS extension :P
Yes - but the reaper.BR_GetMediaTrackByGUID code does not cache anything - and brute forces the search through all tracks every time you call it.

Justin's version builds a cache of the GUIDs - so any subsequent searches will return quickly by using a lookup table if the track has already been cached.

So much quicker probably. I use this sort of method frequently as a lookup table is usually much quicker when you need to repeatedly perform a task.

Thanks Justin.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 05-10-2019, 03:54 AM   #5
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 9,900
Default

Quote:
So much quicker probably. I use this sort of method frequently as a lookup table is usually much quicker when you need to repeatedly perform a task.

Oh yes sure it is a useful trick :P


Cache results is fine if the script doesnt insert or remove tracks.
X-Raym is offline   Reply With Quote
Old 05-10-2019, 04:35 AM   #6
heda
Human being with feelings
 
heda's Avatar
 
Join Date: Jun 2012
Location: Spain
Posts: 7,268
Default

Thank you Justin
heda is offline   Reply With Quote
Old 05-10-2019, 05:59 AM   #7
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

Unless I'm missing something this will only cache the track you're looking for, which rather defeats the purpose of a cache.

I think the last bit should be:
Code:
    local guid = reaper.GetTrackGUID(t)
    -- add to cache
    track_guid_cache[guid] = { idx = x, ptr = t }
    if g == guid then
      -- found      
      return t
    end
Quote:
Originally Posted by X-Raym View Post
Cache results is fine if the script doesnt insert or remove tracks.
Also this. Does the cache need to store the track's index if you're already storing the pointer?
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 05-10-2019, 06:51 AM   #8
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,740
Default

Quote:
Originally Posted by Lokasenna View Post
Unless I'm missing something this will only cache the track you're looking for, which rather defeats the purpose of a cache.
It just means that the first time you ask for a track-by-GUID, it will be O(N) to find the track, but every subsequent time it will be O(1) (or log(N) if that's how lua's table's are implemented).

Adding all of the tracks to the cache on a miss would help if you are planning to access all tracks immediately (with worse performance if you're just looking for a couple of tracks, since building the table isn't free -- I haven't looked, are inserting into Lua tables O(N) or O(log(N)))?

Quote:
Cache results is fine if the script doesnt insert or remove tracks.
Quote:
Also this. Does the cache need to store the track's index if you're already storing the pointer?
Yes! It stores the index so it can quickly detect whether the cache is still valid for the track GUID in question.
Justin is online now   Reply With Quote
Old 05-10-2019, 08:23 AM   #9
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

Quote:
Originally Posted by Justin View Post
It just means that the first time you ask for a track-by-GUID, it will be O(N) to find the track, but every subsequent time it will be O(1) (or log(N) if that's how lua's table's are implemented).

Adding all of the tracks to the cache on a miss would help if you are planning to access all tracks immediately (with worse performance if you're just looking for a couple of tracks, since building the table isn't free
You already have to get the track and GUID for N tracks - putting them in the table would add another operation or two, but I'd be interested to know if it's a significant cost compared to two global table lookups and API calls.

Quote:
I haven't looked, are inserting into Lua tables O(N) or O(log(N)))?
Not sure. There are some details of how tables are handled on the fifth page (labelled p.19) here: https://www.lua.org/gems/sample.pdf

Quote:
Yes! It stores the index so it can quickly detect whether the cache is still valid for the track GUID in question.
Interesting. My understanding is that a track's GUID will never change, while I imagine the MediaTrack pointer is only valid while the project is open and would be different the next time you load it.

Your code looks to me like it's just checking if the pointer still refers to track 5 as a basis for invalidation - does moving tracks around change their pointer?
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 05-10-2019, 08:38 AM   #10
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,740
Default

Quote:
Originally Posted by Lokasenna View Post
You already have to get the track and GUID for N tracks - putting them in the table would add another operation or two, but I'd be interested to know if it's a significant cost compared to two global table lookups and API calls.
For the original use case (a control surface plug-in), the caller would usually only get a dozen or so tracks-from-GUID, for which this code is pretty efficient. If you're often going to be getting all tracks from their GUIDs, it would likely be faster to build a full table of GUID->tracks.

Quote:
Interesting. My understanding is that a track's GUID will never change, while I imagine the MediaTrack pointer is only valid while the project is open and would be different the next time you load it.

Your code looks to me like it's just checking if the pointer still refers to track 5 as a basis for invalidation - does moving tracks around change their pointer?
Tracks could cease to exist. Checking if the pointer matches is a quick way to detect if the track still exists. If it no longer exists, or the tracks were reordered, the pointer will differ, and the cache can be rebuilt.

That code could handle things even more safely by checking to make sure the pointers match, and also checking to make sure the GUID match.

Here's a more aggressively-safe version (just adds "and reaper.GetTrackGUID(c.ptr) == g"):
Code:
local track_guid_cache = {};

function track_from_guid_str(proj, g)
  local c = track_guid_cache[g];
  if c ~= nil and reaper.GetTrack(proj,c.idx) == c.ptr and reaper.GetTrackGUID(c.ptr) == g then
    -- cached!
    return c.ptr;
  end
  
  -- find guid in project
  local x = 0
  while true do
    local t = reaper.GetTrack(proj,x)
    if t == nil then
      -- not found in project, remove from cache and return error
      if c ~= nil then track_guid_cache[g] = nil end
      return nil
    end
    if g == reaper.GetTrackGUID(t) then
      -- found, add to cache
      track_guid_cache[g] = { idx = x, ptr = t }
      return t
    end
    x = x + 1
  end
end
Justin is online now   Reply With Quote
Old 05-10-2019, 08:57 AM   #11
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 15,815
Default

Quote:
Originally Posted by Lokasenna View Post
Not sure. There are some details of how tables are handled on the fifth page (labelled p.19) here: https://www.lua.org/gems/sample.pdf
Pretty interesting When Lua is doing its job right, inserting into an array is O(1), although by design it won't (can't) do its job right all the time. I get the impression Lua started life as a clever hash function.
schwa 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 04:00 PM.


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