Quantcast

* Updated to Astrolabe-coroutine branch

James Whitehead Ii [01-29-08 - 20:52]
* Updated to Astrolabe-coroutine branch
Filename
Astrolabe/Astrolabe.lua
Astrolabe/AstrolabeMapMonitor.lua
diff --git a/Astrolabe/Astrolabe.lua b/Astrolabe/Astrolabe.lua
index e0b5368..e9ca4fd 100644
--- a/Astrolabe/Astrolabe.lua
+++ b/Astrolabe/Astrolabe.lua
@@ -1,19 +1,18 @@
 --[[
 Name: Astrolabe
-Revision: $Rev: 48 $
-$Date: 2007-05-01 19:24:57 -0400 (Tue, 01 May 2007) $
+Revision: $Rev: 52 $
+$Date: 2008-01-29 20:45:51 +0000 (Tue, 29 Jan 2008) $
 Author(s): Esamynn (esamynn@wowinterface.com)
 Inspired By: Gatherer by Norganna
              MapLibrary by Kristofer Karlsson (krka@kth.se)
-Website: http://esamynn.wowinterface.com/
-Documentation: http://www.esamynn.org/wiki/Astrolabe
-SVN: http://esamynn.org/svn/astrolabe/
+Documentation: http://wiki.esamynn.org/Astrolabe
+SVN: http://svn.esamynn.org/astrolabe/
 Description:
 	This is a library for the World of Warcraft UI system to place
 	icons accurately on both the Minimap and the Worldmaps accurately
 	and maintain the accuracy of those positions.

-Copyright (C) 2006-2007 James Carrothers
+Copyright (C) 2006-2008 James Carrothers

 License:
 	This library is free software; you can redistribute it and/or
@@ -42,7 +41,7 @@ Note:
 -- DO NOT MAKE CHANGES TO THIS LIBRARY WITHOUT FIRST CHANGING THE LIBRARY_VERSION_MAJOR
 -- STRING (to something unique) OR ELSE YOU MAY BREAK OTHER ADDONS THAT USE THIS LIBRARY!!!
 local LIBRARY_VERSION_MAJOR = "Astrolabe-0.4"
-local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 48 $", "(%d+)") or 1)
+local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 52 $", "(%d+)") or 1)

 if not DongleStub then error(LIBRARY_VERSION_MAJOR .. " requires DongleStub.") end
 if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) then return end
@@ -50,14 +49,27 @@ if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) t
 local Astrolabe = {};

 -- define local variables for Data Tables (defined at the end of this file)
-local WorldMapSize, MinimapSize;
+local WorldMapSize, MinimapSize, ValidMinimapShapes;

 function Astrolabe:GetVersion()
 	return LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR;
 end

+
+--------------------------------------------------------------------------------------------------------------
+-- Config Constants
+--------------------------------------------------------------------------------------------------------------
+
+local configConstants = {
+	MinimapUpdateMultiplier = true,
+}
+
+-- this constant is multiplied by the current framerate to determine
+-- how many icons are updated each frame
+Astrolabe.MinimapUpdateMultiplier = 1;
+
 --------------------------------------------------------------------------------------------------------------
--- Working Tables and Config Constants
+-- Working Tables
 --------------------------------------------------------------------------------------------------------------

 Astrolabe.LastPlayerPosition = { 0, 0, 0, 0 };
@@ -65,8 +77,6 @@ Astrolabe.MinimapIcons = {};
 Astrolabe.IconsOnEdge = {};
 Astrolabe.IconsOnEdge_GroupChangeCallbacks = {};

-
-Astrolabe.MinimapUpdateTime = 0.2;
 Astrolabe.UpdateTimer = 0;
 Astrolabe.ForceNextUpdate = false;
 Astrolabe.IconsOnEdgeChanged = false;
@@ -75,6 +85,8 @@ Astrolabe.IconsOnEdgeChanged = false;
 -- The state of this variable is controlled by the AstrolabeMapMonitor library.
 Astrolabe.WorldMapVisible = false;

+local AddedOrUpdatedIcons = {}
+local MinimapIconsMetatable = { __index = AddedOrUpdatedIcons }

 --------------------------------------------------------------------------------------------------------------
 -- Internal Utility Functions
@@ -331,14 +343,18 @@ end

 -- local variables specifically for use in this section
 local minimapRotationEnabled = false;
+local minimapShape = false;
 local MinimapCompassRing = MiniMapCompassRing;
 local twoPi = math.pi * 2;
 local atan2 = math.atan2;
 local sin = math.sin;
 local cos = math.cos;
+local abs = math.abs;
+local sqrt = math.sqrt;
+local min = math.min
+local yield = coroutine.yield

 local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, icon, dist, xDist, yDist )
-	--TODO: add support for non-circular minimaps
 	local mapDiameter;
 	if ( Astrolabe.minimapOutside ) then
 		mapDiameter = MinimapSize.outdoor[minimapZoom];
@@ -350,6 +366,7 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic
 	local yScale = mapDiameter / mapHeight;
 	local iconDiameter = ((icon:GetWidth() / 2) + 3) * xScale;
 	local iconOnEdge = nil;
+	local isRound = true;

 	if ( minimapRotationEnabled ) then
 		-- for the life of me, I cannot figure out why the following
@@ -359,6 +376,20 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic
 		yDist = dist * cos(dir);
 	end

+	if ( minimapShape and not (xDist == 0 or yDist == 0) ) then
+		isRound = (xDist < 0) and 1 or 3;
+		if ( yDist < 0 ) then
+			isRound = minimapShape[isRound];
+		else
+			isRound = minimapShape[isRound + 1];
+		end
+	end
+
+	-- for non-circular portions of the Minimap edge
+	if not ( isRound ) then
+		dist = (abs(xDist) > abs(yDist)) and abs(xDist) or abs(yDist);
+	end
+
 	if ( (dist + iconDiameter) > mapRadius ) then
 		-- position along the outside of the Minimap
 		iconOnEdge = true;
@@ -366,6 +397,7 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic
 		xDist = xDist * factor;
 		yDist = yDist * factor;
 	end
+
 	if ( Astrolabe.IconsOnEdge[icon] ~= iconOnEdge ) then
 		Astrolabe.IconsOnEdge[icon] = iconOnEdge;
 		Astrolabe.IconsOnEdgeChanged = true;
@@ -390,11 +422,11 @@ function Astrolabe:PlaceIconOnMinimap( icon, continent, zone, xPos, yPos )
 		--icon's position has no meaningful position relative to the player's current location
 		return -1;
 	end
-	local iconData = self.MinimapIcons[icon];
-	if not ( iconData ) then
-		iconData = GetWorkingTable(icon);
-		self.MinimapIcons[icon] = iconData;
-	end
+	local iconData = GetWorkingTable(icon);
+	iconData = GetWorkingTable(icon);
+	self.MinimapIcons[icon] = nil;
+	AddedOrUpdatedIcons[icon] = iconData
+
 	iconData.continent = continent;
 	iconData.zone = zone;
 	iconData.xPos = xPos;
@@ -409,6 +441,9 @@ function Astrolabe:PlaceIconOnMinimap( icon, continent, zone, xPos, yPos )
 		minimapRotationEnabled = false;
 	end

+	-- check Minimap Shape
+	minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()];
+
 	-- place the icon on the Minimap and :Show() it
 	local map = Minimap
 	placeIconOnMinimap(map, map:GetZoom(), map:GetWidth(), map:GetHeight(), icon, dist, xDist, yDist);
@@ -421,6 +456,7 @@ function Astrolabe:RemoveIconFromMinimap( icon )
 	if not ( self.MinimapIcons[icon] ) then
 		return 1;
 	end
+	AddedOrUpdatedIcons[icon] = nil
 	self.MinimapIcons[icon] = nil;
 	self.IconsOnEdge[icon] = nil;
 	icon:Hide();
@@ -428,6 +464,7 @@ function Astrolabe:RemoveIconFromMinimap( icon )
 end

 function Astrolabe:RemoveAllMinimapIcons()
+	self:DumpNewIconsCache()
 	local minimapIcons = self.MinimapIcons;
 	local IconsOnEdge = self.IconsOnEdge;
 	for k, v in pairs(minimapIcons) do
@@ -437,105 +474,197 @@ function Astrolabe:RemoveAllMinimapIcons()
 	end
 end

-local lastZoom;
-function Astrolabe:UpdateMinimapIconPositions()
-	local C, Z, x, y = self:GetCurrentPlayerPosition();
-	if not ( C and C >= 0 ) then
-		if not ( self.WorldMapVisible ) then
-			self.processingFrame:Hide();
+local lastZoom; -- to remember the last seen Minimap zoom level
+local fullUpdateInProgress = nil
+local resetIncrementalUpdate = false
+
+local function UpdateMinimapIconPositions( self )
+	yield()
+
+	while ( true ) do
+		local C, Z, x, y = self:GetCurrentPlayerPosition();
+		if not ( C and C >= 0 ) then
+			if not ( self.WorldMapVisible ) then
+				self.processingFrame:Hide();
+			end
+			return;
+		end
+		local Minimap = Minimap;
+		local lastPosition = self.LastPlayerPosition;
+		local lC, lZ, lx, ly = unpack(lastPosition);
+
+		if ( GetCVar("rotateMinimap") ~= "0" ) then
+			minimapRotationEnabled = true;
+		else
+			minimapRotationEnabled = false;
+		end
+
+		-- check current frame rate
+		local numPerCycle = min(50, GetFramerate() * (self.MinimapUpdateMultiplier or 1))
+
+		-- check Minimap Shape
+		minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()];
+
+		if ( lC == C and lZ == Z and lx == x and ly == y ) then
+			-- player has not moved since the last update
+			if ( lastZoom ~= Minimap:GetZoom() or self.ForceNextUpdate or minimapRotationEnabled ) then
+				local currentZoom = Minimap:GetZoom();
+				lastZoom = currentZoom;
+				local mapWidth = Minimap:GetWidth();
+				local mapHeight = Minimap:GetHeight();
+				numPerCycle = numPerCycle * 2
+				local count = 0
+				for icon, data in pairs(self.MinimapIcons) do
+					placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, data.dist, data.xDist, data.yDist);
+
+					count = count + 1
+					if ( count > numPerCycle ) then
+						count = 0
+						yield()
+					end
+				end
+				self.ForceNextUpdate = false;
+			end
+		else
+			local dist, xDelta, yDelta = self:ComputeDistance(lC, lZ, lx, ly, C, Z, x, y);
+			if ( dist ) then
+				local currentZoom = Minimap:GetZoom();
+				lastZoom = currentZoom;
+				local mapWidth = Minimap:GetWidth();
+				local mapHeight = Minimap:GetHeight();
+				local count = 0
+				for icon, data in pairs(self.MinimapIcons) do
+					local xDist = data.xDist - xDelta;
+					local yDist = data.yDist - yDelta;
+					local dist = sqrt(xDist*xDist + yDist*yDist);
+					placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist);
+
+					data.dist = dist;
+					data.xDist = xDist;
+					data.yDist = yDist;
+
+					count = count + 1
+					if ( count > numPerCycle ) then
+						count = 0
+						yield()
+						if ( resetIncrementalUpdate ) then
+							break;
+						end
+					end
+				end
+			else
+				self:RemoveAllMinimapIcons()
+			end
+
+			if not ( resetIncrementalUpdate ) then
+				lastPosition[1] = C;
+				lastPosition[2] = Z;
+				lastPosition[3] = x;
+				lastPosition[4] = y;
+			end
+		end
+
+		-- put new/update icons into the main datacache
+		self:DumpNewIconsCache()
+
+		if not ( resetIncrementalUpdate ) then
+			yield()
 		end
-		return;
 	end
-	local Minimap = Minimap;
-	local lastPosition = self.LastPlayerPosition;
-	local lC, lZ, lx, ly = unpack(lastPosition);
-
-	if ( GetCVar("rotateMinimap") ~= "0" ) then
-		minimapRotationEnabled = true;
-	else
-		minimapRotationEnabled = false;
+end
+
+local incrementalUpdateCrashed = true
+local incrementalUpdateThread = coroutine.wrap(UpdateMinimapIconPositions)
+function Astrolabe:UpdateMinimapIconPositions()
+	if ( incrementalUpdateCrashed ) then
+		incrementalUpdateThread = coroutine.wrap(UpdateMinimapIconPositions)
+		incrementalUpdateThread(self)
+	end
+	if ( fullUpdateInProgress ) then
+		fullUpdateInProgress()
 	end
+	incrementalUpdateCrashed = true
+	incrementalUpdateThread()
+	incrementalUpdateCrashed = false
+end
+
+function CalculateMinimapIconPositions( self )
+	yield()

-	if ( lC == C and lZ == Z and lx == x and ly == y ) then
-		-- player has not moved since the last update
-		if ( lastZoom ~= Minimap:GetZoom() or self.ForceNextUpdate or minimapRotationEnabled ) then
-			local currentZoom = Minimap:GetZoom();
-			lastZoom = currentZoom;
-			local mapWidth = Minimap:GetWidth();
-			local mapHeight = Minimap:GetHeight();
-			for icon, data in pairs(self.MinimapIcons) do
-				placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, data.dist, data.xDist, data.yDist);
+	while ( true ) do
+		local C, Z, x, y = self:GetCurrentPlayerPosition();
+		if not ( C and C >= 0 ) then
+			if not ( self.WorldMapVisible ) then
+				self.processingFrame:Hide();
 			end
-			self.ForceNextUpdate = false;
+			return;
 		end
-	else
-		local dist, xDelta, yDelta = self:ComputeDistance(lC, lZ, lx, ly, C, Z, x, y);
-		if ( dist ) then
-			local currentZoom = Minimap:GetZoom();
-			lastZoom = currentZoom;
-			local mapWidth = Minimap:GetWidth();
-			local mapHeight = Minimap:GetHeight();
-			for icon, data in pairs(self.MinimapIcons) do
-				local xDist = data.xDist - xDelta;
-				local yDist = data.yDist - yDelta;
-				local dist = sqrt(xDist*xDist + yDist*yDist);
+
+		-- put new/update icons into the main datacache
+		self:DumpNewIconsCache()
+
+		if ( GetCVar("rotateMinimap") ~= "0" ) then
+			minimapRotationEnabled = true;
+		else
+			minimapRotationEnabled = false;
+		end
+
+		-- check current frame rate
+		local numPerCycle = GetFramerate() * (self.MinimapUpdateMultiplier or 1) * 2
+
+		-- check Minimap Shape
+		minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()];
+
+		local currentZoom = Minimap:GetZoom();
+		lastZoom = currentZoom;
+		local Minimap = Minimap;
+		local mapWidth = Minimap:GetWidth();
+		local mapHeight = Minimap:GetHeight();
+		local count = 0
+		for icon, data in pairs(self.MinimapIcons) do
+			local dist, xDist, yDist = self:ComputeDistance(C, Z, x, y, data.continent, data.zone, data.xPos, data.yPos);
+			if ( dist ) then
 				placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist);

 				data.dist = dist;
 				data.xDist = xDist;
 				data.yDist = yDist;
+			else
+				self:RemoveIconFromMinimap(icon)
+			end
+
+			count = count + 1
+			if ( count > numPerCycle ) then
+				count = 0
+				yield()
 			end
-		else
-			self:RemoveAllMinimapIcons()
 		end

+		local lastPosition = self.LastPlayerPosition;
 		lastPosition[1] = C;
 		lastPosition[2] = Z;
 		lastPosition[3] = x;
 		lastPosition[4] = y;
+
+		fullUpdateInProgress = nil
+		yield()
 	end
 end

+local updateCrashed = true
+local updateThread = coroutine.wrap(CalculateMinimapIconPositions)
 function Astrolabe:CalculateMinimapIconPositions()
-	local C, Z, x, y = self:GetCurrentPlayerPosition();
-	if not ( C and C >= 0 ) then
-		if not ( self.WorldMapVisible ) then
-			self.processingFrame:Hide();
-		end
-		return;
-	end
-
-	if ( GetCVar("rotateMinimap") ~= "0" ) then
-		minimapRotationEnabled = true;
-	else
-		minimapRotationEnabled = false;
-	end
-
-	local currentZoom = Minimap:GetZoom();
-	lastZoom = currentZoom;
-	local Minimap = Minimap;
-	local mapWidth = Minimap:GetWidth();
-	local mapHeight = Minimap:GetHeight();
-	for icon, data in pairs(self.MinimapIcons) do
-		local dist, xDist, yDist = self:ComputeDistance(C, Z, x, y, data.continent, data.zone, data.xPos, data.yPos);
-		if ( dist ) then
-			placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist);
-
-			data.dist = dist;
-			data.xDist = xDist;
-			data.yDist = yDist;
-		else
-			self:RemoveIconFromMinimap(icon)
-		end
+	if ( updateCrashed ) then
+		updateThread = coroutine.wrap(CalculateMinimapIconPositions)
+		updateThread(self)
 	end
-
-	local lastPosition = self.LastPlayerPosition;
-	lastPosition[1] = C;
-	lastPosition[2] = Z;
-	lastPosition[3] = x;
-	lastPosition[4] = y;
+	updateCrashed = true
+	fullUpdateInProgress = updateThread -- save the thread so it will be finished
+	updateThread()
+	updateCrashed = false
 end

+
 function Astrolabe:GetDistanceToIcon( icon )
 	local data = self.MinimapIcons[icon];
 	if ( data ) then
@@ -566,6 +695,15 @@ function Astrolabe:Register_OnEdgeChanged_Callback( func, ident )
 	self.IconsOnEdge_GroupChangeCallbacks[func] = ident;
 end

+-- for use ONLY by the activate function
+function Astrolabe:DumpNewIconsCache()
+	local MinimapIcons = self.MinimapIcons
+	for icon, data in pairs(AddedOrUpdatedIcons) do
+		MinimapIcons[icon] = data
+		AddedOrUpdatedIcons[icon] = nil
+	end
+end
+

 --------------------------------------------------------------------------------------------------------------
 -- World Map Icon Placement
@@ -637,6 +775,7 @@ function Astrolabe:OnEvent( frame, event )
 		end

 	elseif ( event == "ZONE_CHANGED_NEW_AREA" ) then
+		frame:Hide();
 		frame:Show();

 	end
@@ -650,7 +789,7 @@ function Astrolabe:OnUpdate( frame, elapsed )
 			pcall(func);
 		end
 	end
-
+	--[[
 	-- icon position updates
 	local updateTimer = self.UpdateTimer - elapsed;
 	if ( updateTimer > 0 ) then
@@ -658,11 +797,15 @@ function Astrolabe:OnUpdate( frame, elapsed )
 		return;
 	end
 	self.UpdateTimer = self.MinimapUpdateTime;
+	]]
 	self:UpdateMinimapIconPositions();
 end

 function Astrolabe:OnShow( frame )
 	-- set the world map to a zoom with a valid player position
+	if not ( self.WorldMapVisible ) then
+		SetMapToCurrentZone();
+	end
 	local C, Z = Astrolabe:GetCurrentPlayerPosition();
 	if ( C and C >= 0 ) then
 		SetMapZoom(C, Z);
@@ -676,7 +819,8 @@ end

 -- called by AstrolabMapMonitor when all world maps are hidden
 function Astrolabe:AllWorldMapsHidden()
-	self:CalculateMinimapIconPositions();
+	self.processingFrame:Hide();
+	self.processingFrame:Show();
 end


@@ -686,8 +830,11 @@ end

 local function activate( newInstance, oldInstance )
 	if ( oldInstance ) then -- this is an upgrade activate
+		if ( oldInstance.DumpNewIconsCache ) then
+			oldInstance:DumpNewIconsCache()
+		end
 		for k, v in pairs(oldInstance) do
-			if ( type(v) ~= "function" ) then
+			if ( type(v) ~= "function" and (not configConstants[k]) ) then
 				newInstance[k] = v;
 			end
 		end
@@ -707,6 +854,7 @@ local function activate( newInstance, oldInstance )
 			end
 		end
 	end
+	configConstants = nil -- we don't need this anymore

 	local frame = newInstance.processingFrame;
 	frame:SetParent("Minimap");
@@ -731,6 +879,8 @@ local function activate( newInstance, oldInstance )
 		end
 	);

+	setmetatable(Astrolabe.MinimapIcons, MinimapIconsMetatable)
+
 	-- register this library with AstrolabeMapMonitor
 	local AstrolabeMapMonitor = DongleStub("AstrolabeMapMonitor");
 	AstrolabeMapMonitor:RegisterAstrolabeLibrary(Astrolabe, LIBRARY_VERSION_MAJOR);
@@ -764,6 +914,23 @@ MinimapSize = {
 	},
 }

+ValidMinimapShapes = {
+	-- { upper-left, lower-left, upper-right, lower-right }
+	["SQUARE"]                = { false, false, false, false },
+	["CORNER-TOPLEFT"]        = { true,  false, false, false },
+	["CORNER-TOPRIGHT"]       = { false, false, true,  false },
+	["CORNER-BOTTOMLEFT"]     = { false, true,  false, false },
+	["CORNER-BOTTOMRIGHT"]    = { false, false, false, true },
+	["SIDE-LEFT"]             = { true,  true,  false, false },
+	["SIDE-RIGHT"]            = { false, false, true,  true },
+	["SIDE-TOP"]              = { true,  false, true,  false },
+	["SIDE-BOTTOM"]           = { false, true,  false, true },
+	["TRICORNER-TOPLEFT"]     = { true,  true,  true,  false },
+	["TRICORNER-TOPRIGHT"]    = { true,  false, true,  true },
+	["TRICORNER-BOTTOMLEFT"]  = { true,  true,  false, true },
+	["TRICORNER-BOTTOMRIGHT"] = { false, true,  true,  true },
+}
+
 -- distances across and offsets of the world maps
 -- in game yards
 WorldMapSize = {
diff --git a/Astrolabe/AstrolabeMapMonitor.lua b/Astrolabe/AstrolabeMapMonitor.lua
index ae81c27..58b83cc 100644
--- a/Astrolabe/AstrolabeMapMonitor.lua
+++ b/Astrolabe/AstrolabeMapMonitor.lua
@@ -1,7 +1,7 @@
 --[[
 Name: AstrolabeMapMonitor
 Revision: $Rev: 44 $
-$Date: 2007-03-30 14:56:21 -0400 (Fri, 30 Mar 2007) $
+$Date: 2007-03-30 19:56:21 +0100 (Fri, 30 Mar 2007) $
 Author(s): Esamynn (esamynn@wowinterface.com)
 Inspired By: Gatherer by Norganna
              MapLibrary by Kristofer Karlsson (krka@kth.se)