Browse Source

Add the ability to have formally deprecated functions.

Functions which are deprecated will print out a message and show up in a small dialog on-screen, when they're first called. Deprecation output is disabled in fused mode by default, and can be modified with love.setDeprecationOutput(enable).

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
de28c6e6a8

+ 4 - 0
CMakeLists.txt

@@ -261,6 +261,8 @@ set(LOVE_SRC_COMMON
 	src/common/Data.h
 	src/common/delay.cpp
 	src/common/delay.h
+	src/common/deprecation.cpp
+	src/common/deprecation.h
 	src/common/EnumMap.h
 	src/common/Exception.cpp
 	src/common/Exception.h
@@ -477,6 +479,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Canvas.h
 	src/modules/graphics/depthstencil.cpp
 	src/modules/graphics/depthstencil.h
+	src/modules/graphics/Deprecations.cpp
+	src/modules/graphics/Deprecations.h
 	src/modules/graphics/Drawable.cpp
 	src/modules/graphics/Drawable.h
 	src/modules/graphics/Font.cpp

+ 20 - 0
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -935,10 +935,16 @@
 		FA91591E1CF1ED7500A7053F /* halffloat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91591C1CF1ED7500A7053F /* halffloat.cpp */; };
 		FA91591F1CF1ED7500A7053F /* halffloat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91591C1CF1ED7500A7053F /* halffloat.cpp */; };
 		FA9159201CF1ED7500A7053F /* halffloat.h in Headers */ = {isa = PBXBuildFile; fileRef = FA91591D1CF1ED7500A7053F /* halffloat.h */; };
+		FA91DA8B1F377C3900C80E33 /* deprecation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91DA891F377C3900C80E33 /* deprecation.cpp */; };
+		FA91DA8C1F377C3900C80E33 /* deprecation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA91DA891F377C3900C80E33 /* deprecation.cpp */; };
+		FA91DA8D1F377C3900C80E33 /* deprecation.h in Headers */ = {isa = PBXBuildFile; fileRef = FA91DA8A1F377C3900C80E33 /* deprecation.h */; };
 		FA93C4521F315B960087CCD4 /* CompressedFormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA93C44F1F315B960087CCD4 /* CompressedFormatHandler.h */; };
 		FA93C4531F315B960087CCD4 /* FormatHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FA93C4501F315B960087CCD4 /* FormatHandler.h */; };
 		FA93C4541F315B960087CCD4 /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA93C4511F315B960087CCD4 /* FormatHandler.cpp */; };
 		FA9B4A0816E1578300074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0716E1578300074F42 /* SDL2.framework */; };
+		FA9D53AC1F5307E900125C6B /* Deprecations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D53AA1F5307E900125C6B /* Deprecations.cpp */; };
+		FA9D53AD1F5307E900125C6B /* Deprecations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D53AA1F5307E900125C6B /* Deprecations.cpp */; };
+		FA9D53AE1F5307E900125C6B /* Deprecations.h in Headers */ = {isa = PBXBuildFile; fileRef = FA9D53AB1F5307E900125C6B /* Deprecations.h */; };
 		FA9D8DD11DEB56C3002CD881 /* pixelformat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */; };
 		FA9D8DD21DEB56C3002CD881 /* pixelformat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */; };
 		FA9D8DD31DEB56C3002CD881 /* pixelformat.h in Headers */ = {isa = PBXBuildFile; fileRef = FA9D8DD01DEB56C3002CD881 /* pixelformat.h */; };
@@ -1793,10 +1799,14 @@
 		FA8951A11AA2EDF300EC385A /* wrap_Event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Event.h; sourceTree = "<group>"; };
 		FA91591C1CF1ED7500A7053F /* halffloat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = halffloat.cpp; sourceTree = "<group>"; };
 		FA91591D1CF1ED7500A7053F /* halffloat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = halffloat.h; sourceTree = "<group>"; };
+		FA91DA891F377C3900C80E33 /* deprecation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deprecation.cpp; sourceTree = "<group>"; };
+		FA91DA8A1F377C3900C80E33 /* deprecation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = deprecation.h; sourceTree = "<group>"; };
 		FA93C44F1F315B960087CCD4 /* CompressedFormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedFormatHandler.h; sourceTree = "<group>"; };
 		FA93C4501F315B960087CCD4 /* FormatHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormatHandler.h; sourceTree = "<group>"; };
 		FA93C4511F315B960087CCD4 /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = "<group>"; };
 		FA9B4A0716E1578300074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = /Library/Frameworks/SDL2.framework; sourceTree = "<absolute>"; };
+		FA9D53AA1F5307E900125C6B /* Deprecations.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Deprecations.cpp; sourceTree = "<group>"; };
+		FA9D53AB1F5307E900125C6B /* Deprecations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Deprecations.h; sourceTree = "<group>"; };
 		FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pixelformat.cpp; sourceTree = "<group>"; };
 		FA9D8DD01DEB56C3002CD881 /* pixelformat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixelformat.h; sourceTree = "<group>"; };
 		FA9D8DD41DEF8411002CD881 /* Data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Data.cpp; sourceTree = "<group>"; };
@@ -2025,6 +2035,8 @@
 				FA0B78FA1A958E3B000E1D17 /* Data.h */,
 				FA0B78FB1A958E3B000E1D17 /* delay.cpp */,
 				FA0B78FC1A958E3B000E1D17 /* delay.h */,
+				FA91DA891F377C3900C80E33 /* deprecation.cpp */,
+				FA91DA8A1F377C3900C80E33 /* deprecation.h */,
 				FA0B78FD1A958E3B000E1D17 /* EnumMap.h */,
 				FA0B78FE1A958E3B000E1D17 /* Exception.cpp */,
 				FA0B78FF1A958E3B000E1D17 /* Exception.h */,
@@ -2651,6 +2663,8 @@
 				FADF53F71E3C7ACD00012CC0 /* Buffer.h */,
 				FA1BA0A51E16F20600AA2803 /* Canvas.cpp */,
 				FA1BA0A61E16F20600AA2803 /* Canvas.h */,
+				FA9D53AA1F5307E900125C6B /* Deprecations.cpp */,
+				FA9D53AB1F5307E900125C6B /* Deprecations.h */,
 				FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */,
 				FAF1889D1E9DBBC8008C1479 /* depthstencil.h */,
 				FA9D8DDC1DEF842A002CD881 /* Drawable.cpp */,
@@ -3740,6 +3754,7 @@
 				FA0B7D2A1A95902C000E1D17 /* wrap_GlyphData.h in Headers */,
 				FA0B7E741A95902C000E1D17 /* wrap_Shape.h in Headers */,
 				FA0B7A5D1A958EA3000E1D17 /* b2Timer.h in Headers */,
+				FA91DA8D1F377C3900C80E33 /* deprecation.h in Headers */,
 				FA0B7A4A1A958EA3000E1D17 /* b2Shape.h in Headers */,
 				FAF1407F1E20934C00F898D2 /* localintermediate.h in Headers */,
 				FAF1406B1E20934C00F898D2 /* glslang_tab.cpp.h in Headers */,
@@ -3777,6 +3792,7 @@
 				FA0B7E2C1A95902C000E1D17 /* RevoluteJoint.h in Headers */,
 				FA8951A41AA2EDF300EC385A /* wrap_Event.h in Headers */,
 				FA0B7B2A1A958EA3000E1D17 /* simplexnoise1234.h in Headers */,
+				FA9D53AE1F5307E900125C6B /* Deprecations.h in Headers */,
 				FA0B7ADC1A958EA3000E1D17 /* glad.hpp in Headers */,
 				FA0B7CF91A95902C000E1D17 /* FileData.h in Headers */,
 				FA0B7DA71A95902C000E1D17 /* PNGHandler.h in Headers */,
@@ -4024,6 +4040,7 @@
 				FA0B7DB81A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B794B1A958E3B000E1D17 /* wrap_Data.cpp in Sources */,
 				FAB17BEC1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */,
+				FA91DA8C1F377C3900C80E33 /* deprecation.cpp in Sources */,
 				FA0B7E401A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */,
 				FA0B7DEC1A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7D871A95902C000E1D17 /* ImageData.cpp in Sources */,
@@ -4078,6 +4095,7 @@
 				FA0B7D3D1A95902C000E1D17 /* Image.cpp in Sources */,
 				FA0B7B351A958EA3000E1D17 /* wuff_convert.c in Sources */,
 				FAF140941E20934C00F898D2 /* PpScanner.cpp in Sources */,
+				FA9D53AD1F5307E900125C6B /* Deprecations.cpp in Sources */,
 				FA0B7E431A95902C000E1D17 /* wrap_CircleShape.cpp in Sources */,
 				FA0B7CE61A95902C000E1D17 /* wrap_Source.cpp in Sources */,
 				FA0B7AA21A958EA3000E1D17 /* b2PulleyJoint.cpp in Sources */,
@@ -4381,6 +4399,7 @@
 				FA0B7DB71A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7A321A958EA3000E1D17 /* b2Collision.cpp in Sources */,
 				FAB17BEB1ABFAF1800F9BA27 /* CompressedData.cpp in Sources */,
+				FA91DA8B1F377C3900C80E33 /* deprecation.cpp in Sources */,
 				FA0B7E3F1A95902C000E1D17 /* wrap_ChainShape.cpp in Sources */,
 				FA0B7DEB1A95902C000E1D17 /* Cursor.cpp in Sources */,
 				FA0B7D861A95902C000E1D17 /* ImageData.cpp in Sources */,
@@ -4435,6 +4454,7 @@
 				FA0B7CE51A95902C000E1D17 /* wrap_Source.cpp in Sources */,
 				FA0B792C1A958E3B000E1D17 /* Memoizer.cpp in Sources */,
 				FAF140931E20934C00F898D2 /* PpScanner.cpp in Sources */,
+				FA9D53AC1F5307E900125C6B /* Deprecations.cpp in Sources */,
 				FA0B7CCD1A95902C000E1D17 /* Audio.cpp in Sources */,
 				FA0B7DCA1A95902C000E1D17 /* Keyboard.cpp in Sources */,
 				FA0B7AA41A958EA3000E1D17 /* b2RevoluteJoint.cpp in Sources */,

+ 8 - 0
src/common/Module.cpp

@@ -21,6 +21,7 @@
 // LOVE
 #include "Module.h"
 #include "Exception.h"
+#include "deprecation.h"
 
 // std
 #include <map>
@@ -61,6 +62,11 @@ namespace love
 love::Type Module::type("Module", &Object::type);
 Module *Module::instances[] = {};
 
+Module::Module()
+{
+	initDeprecation();
+}
+
 Module::~Module()
 {
 	ModuleRegistry &registry = registryInstance();
@@ -83,6 +89,8 @@ Module::~Module()
 	}
 
 	freeEmptyRegistry();
+
+	deinitDeprecation();
 }
 
 void Module::registerInstance(Module *instance)

+ 1 - 0
src/common/Module.h

@@ -59,6 +59,7 @@ public:
 		M_MAX_ENUM
 	};
 
+	Module();
 	virtual ~Module();
 
     /**

+ 157 - 0
src/common/deprecation.cpp

@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "common/config.h"
+#include "deprecation.h"
+#include "thread/threads.h"
+
+#include <atomic>
+#include <map>
+
+namespace love
+{
+
+static std::map<std::string, DeprecationInfo> deprecated;
+static std::vector<const DeprecationInfo *> deprecatedList;
+
+static std::atomic<int> initCount;
+
+static thread::Mutex *mutex = nullptr;
+static bool outputEnabled = false;
+
+void initDeprecation()
+{
+	if (initCount.fetch_add(1) == 0)
+		mutex = thread::newMutex();
+}
+
+void deinitDeprecation()
+{
+	if (initCount.fetch_sub(1) == 1)
+	{
+		deprecatedList.clear();
+		deprecated.clear();
+
+		delete mutex;
+		mutex = nullptr;
+	}
+}
+
+static void printDeprecationNotice(const DeprecationInfo &info)
+{
+	std::string notice = getDeprecationNotice(info, true);
+	printf("LOVE - Warning: %s\n", notice.c_str());
+}
+
+void setDeprecationOutputEnabled(bool enable)
+{
+	if (enable == outputEnabled)
+		return;
+
+	outputEnabled = enable;
+
+	if (enable)
+	{
+		GetDeprecated deprecated;
+
+		for (const DeprecationInfo *info : deprecated.all)
+		{
+			if (info->uses == 1)
+				printDeprecationNotice(*info);
+		}
+	}
+}
+
+std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere)
+{
+	std::string notice;
+
+	if (usewhere)
+		notice += info.where;
+
+	notice += "Using deprecated function " + info.name;
+
+	if (info.type == DEPRECATED_REPLACEMENT && !info.replacement.empty())
+		notice += " (replaced by " + info.replacement + ")";
+	else if (info.type == DEPRECATED_RENAMED && !info.replacement.empty())
+		notice += " (renamed to " + info.replacement + ")";
+
+	return notice;
+}
+
+GetDeprecated::GetDeprecated()
+	: all(deprecatedList)
+{
+	if (mutex != nullptr)
+		mutex->lock();
+}
+
+GetDeprecated::~GetDeprecated()
+{
+	if (mutex != nullptr)
+		mutex->unlock();
+}
+
+MarkDeprecated::MarkDeprecated(const char *name)
+	: MarkDeprecated(name, DEPRECATED_NO_REPLACEMENT, nullptr)
+{
+}
+
+MarkDeprecated::MarkDeprecated(const char *name, DeprecationType type, const char *replacement)
+	: info(nullptr)
+{
+	if (mutex != nullptr)
+		mutex->lock();
+
+	auto it = deprecated.find(name);
+
+	if (it != deprecated.end())
+	{
+		it->second.uses++;
+		info = &it->second;
+	}
+	else
+	{
+		DeprecationInfo newinfo = {};
+
+		newinfo.type = type;
+		newinfo.uses = 1;
+		newinfo.name = name;
+
+		if (replacement != nullptr)
+			newinfo.replacement = replacement;
+
+		auto inserted = deprecated.insert(std::make_pair(newinfo.name, newinfo));
+
+		info = &inserted.first->second;
+		deprecatedList.push_back(info);
+	}
+}
+
+MarkDeprecated::~MarkDeprecated()
+{
+	if (outputEnabled && info != nullptr && info->uses == 1)
+		printDeprecationNotice(*info);
+
+	if (mutex != nullptr)
+		mutex->unlock();
+}
+
+} // love

+ 72 - 0
src/common/deprecation.h

@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+#include "int.h"
+
+#include <string>
+#include <vector>
+
+namespace love
+{
+
+enum DeprecationType
+{
+	DEPRECATED_NO_REPLACEMENT,
+	DEPRECATED_REPLACEMENT,
+	DEPRECATED_RENAMED,
+};
+
+struct DeprecationInfo
+{
+	DeprecationType type;
+	int64 uses;
+	std::string name;
+	std::string replacement;
+	std::string where;
+};
+
+void initDeprecation();
+void deinitDeprecation();
+
+void setDeprecationOutputEnabled(bool enable);
+bool isDeprecationOutputEnabled();
+
+std::string getDeprecationNotice(const DeprecationInfo &info, bool usewhere);
+
+struct GetDeprecated
+{
+	GetDeprecated();
+	~GetDeprecated();
+
+	const std::vector<const DeprecationInfo *> &all;
+};
+
+struct MarkDeprecated
+{
+	MarkDeprecated(const char *name);
+	MarkDeprecated(const char *name, DeprecationType type, const char *replacement);
+	~MarkDeprecated();
+
+	DeprecationInfo *info;
+};
+
+} // love

+ 19 - 0
src/common/runtime.cpp

@@ -786,6 +786,25 @@ lua_State *luax_getpinnedthread(lua_State *L)
 	return thread;
 }
 
+void luax_markdeprecated(lua_State *L, const char *name)
+{
+	luax_markdeprecated(L, name, DEPRECATED_NO_REPLACEMENT, nullptr);
+}
+
+void luax_markdeprecated(lua_State *L, const char *name, DeprecationType type, const char *replacement)
+{
+	MarkDeprecated deprecated(name, type, replacement);
+
+	if (deprecated.info != nullptr && deprecated.info->uses == 1)
+	{
+		luaL_where(L, 1);
+		const char *where = lua_tostring(L, -1);
+		if (where != nullptr)
+			deprecated.info->where = where;
+		lua_pop(L, 1);
+	}
+}
+
 extern "C" int luax_typerror(lua_State *L, int narg, const char *tname)
 {
 	int argtype = lua_type(L, narg);

+ 8 - 0
src/common/runtime.h

@@ -23,6 +23,7 @@
 
 // LOVE
 #include "types.h"
+#include "deprecation.h"
 
 // Lua
 extern "C" {
@@ -447,6 +448,13 @@ lua_State *luax_insistpinnedthread(lua_State *L);
  **/
 lua_State *luax_getpinnedthread(lua_State *L);
 
+/**
+ * Mark a function as deprecated. Should only be called inside wrapper function
+ * code.
+ **/
+void luax_markdeprecated(lua_State *L, const char *name);
+void luax_markdeprecated(lua_State *L, const char *name, DeprecationType type, const char *replacement);
+
 extern "C" { // Also called from luasocket
 	int luax_typerror(lua_State *L, int narg, const char *tname);
 }

+ 136 - 0
src/modules/graphics/Deprecations.cpp

@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "common/config.h"
+#include "Deprecations.h"
+#include "Graphics.h"
+#include "Font.h"
+#include "common/deprecation.h"
+#include "timer/Timer.h"
+
+#include <algorithm>
+
+namespace love
+{
+namespace graphics
+{
+
+Deprecations::Deprecations()
+	: currentDeprecationCount(0)
+	, lastUpdatedTime(0.0)
+{
+}
+
+Deprecations::~Deprecations()
+{
+}
+
+void Deprecations::draw(Graphics *gfx)
+{
+	GetDeprecated deprecations;
+
+	if (deprecations.all.empty())
+		return;
+
+	int total = (int) deprecations.all.size();
+
+	if (total != currentDeprecationCount)
+	{
+		currentDeprecationCount = total;
+		lastUpdatedTime = timer::Timer::getTime();
+	}
+
+	double showTime = 20.0;
+	double fadeTime = 1.0;
+
+	double delta = timer::Timer::getTime() - lastUpdatedTime;
+
+	float alpha = 1.0f;
+	if (delta > (showTime - fadeTime))
+		alpha = (float) (1.0 - (delta - showTime - fadeTime) / fadeTime);
+
+	if (alpha <= 0.0f)
+		return;
+
+	if (font.get() == nullptr)
+	{
+		auto hinting = font::TrueTypeRasterizer::HINTING_NORMAL;
+
+		if (!isGammaCorrect() && gfx->getScreenPixelDensity() <= 1.0)
+			hinting = font::TrueTypeRasterizer::HINTING_LIGHT;
+
+		font.set(gfx->newDefaultFont(9, hinting), Acquire::NORETAIN);
+	}
+
+	gfx->flushStreamDraws();
+
+	gfx->push(Graphics::STACK_ALL);
+	gfx->reset();
+
+	int maxcount = 4;
+	int remaining = std::max(0, total - maxcount);
+
+	std::vector<Font::ColoredString> strings;
+	Colorf white(1, 1, 1, 1);
+
+	// Grab the newest deprecation notices first.
+	for (int i = total - 1; i >= remaining; --i)
+	{
+		if (!strings.empty())
+			strings.back().str += "\n";
+
+		const DeprecationInfo *info = deprecations.all[i];
+		strings.push_back({getDeprecationNotice(*info, true), white});
+	}
+
+	if (remaining > 0)
+		strings.push_back({"\n(And " + std::to_string(remaining) + " more)", white});
+
+	int padding = 5;
+	int width = 600;
+
+	for (const auto &coloredstr : strings)
+		width = std::max(width, font->getWidth(coloredstr.str) + padding * 2);
+
+	float wraplimit = std::min(gfx->getWidth(), width - padding * 2);
+
+	std::vector<std::string> wrappedlines;
+	font->getWrap(strings, wraplimit, wrappedlines);
+
+	int linecount = std::min((int) wrappedlines.size(), maxcount);
+	int height = font->getHeight() * linecount + padding * 2;
+
+	int x = 0;
+	int y = std::max(gfx->getHeight() - height, 0);
+
+	gfx->setColor(Colorf(0, 0, 0, 0.85 * alpha));
+	gfx->rectangle(Graphics::DRAW_FILL, x, y, width, height);
+
+	gfx->setColor(Colorf(1, 0.9, 0.8, 1 * alpha));
+	gfx->setScissor({x, y, width, height});
+
+	Matrix4 textm(x + padding, y + padding, 0, 1, 1, 0, 0, 0, 0);
+	gfx->printf(strings, font.get(), wraplimit, Font::ALIGN_LEFT, textm);
+
+	gfx->pop();
+}
+
+} // graphics
+} // love

+ 51 - 0
src/modules/graphics/Deprecations.h

@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+#include "common/Object.h"
+
+namespace love
+{
+namespace graphics
+{
+
+class Graphics;
+class Font;
+
+class Deprecations
+{
+public:
+
+	Deprecations();
+	~Deprecations();
+
+	void draw(Graphics *gfx);
+
+private:
+
+	int currentDeprecationCount;
+	double lastUpdatedTime;
+	StrongRef<Font> font;
+
+}; // Deprecations
+
+} // graphics
+} // love

+ 12 - 10
src/modules/graphics/Graphics.cpp

@@ -27,6 +27,7 @@
 #include "window/Window.h"
 #include "Font.h"
 #include "Video.h"
+#include "common/deprecation.h"
 
 // C++
 #include <algorithm>
@@ -157,6 +158,16 @@ Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &fil
 	return new Font(data, filter);
 }
 
+Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter)
+{
+	auto fontmodule = Module::getInstance<font::Font>(M_FONT);
+	if (!fontmodule)
+		throw love::Exception("Font module has not been loaded.");
+
+	StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN);
+	return newFont(r.get(), filter);
+}
+
 Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
 {
 	return new Video(this, stream, pixeldensity);
@@ -357,16 +368,7 @@ void Graphics::checkSetDefaultFont()
 
 	// Create a new default font if we don't have one yet.
 	if (!defaultFont.get())
-	{
-		auto fontmodule = Module::getInstance<font::Font>(M_FONT);
-		if (!fontmodule)
-			throw love::Exception("Font module has not been loaded.");
-
-		auto hinting = font::TrueTypeRasterizer::HINTING_NORMAL;
-		StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(12, hinting), Acquire::NORETAIN);
-
-		defaultFont.set(newFont(r.get()), Acquire::NORETAIN);
-	}
+		defaultFont.set(newDefaultFont(12, font::TrueTypeRasterizer::HINTING_NORMAL), Acquire::NORETAIN);
 
 	states.back().font.set(defaultFont.get());
 }

+ 5 - 0
src/modules/graphics/Graphics.h

@@ -38,9 +38,11 @@
 #include "Quad.h"
 #include "Mesh.h"
 #include "Image.h"
+#include "Deprecations.h"
 #include "depthstencil.h"
 #include "math/Transform.h"
 #include "font/Rasterizer.h"
+#include "font/Font.h"
 #include "video/VideoStream.h"
 
 // C++
@@ -369,6 +371,7 @@ public:
 
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 	Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter);
+	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter = Texture::defaultFilter);
 	Video *newVideo(love::video::VideoStream *stream, float pixeldensity);
 
 	virtual SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage) = 0;
@@ -905,6 +908,8 @@ protected:
 
 	Capabilities capabilities;
 
+	Deprecations deprecations;
+
 	static const size_t MAX_USER_STACK_DEPTH = 64;
 
 private:

+ 2 - 0
src/modules/graphics/opengl/Graphics.cpp

@@ -899,6 +899,8 @@ void Graphics::present(void *screenshotCallbackData)
 	if (isCanvasActive())
 		throw love::Exception("present cannot be called while a Canvas is active.");
 
+	deprecations.draw(this);
+
 	flushStreamDraws();
 	endPass();
 

+ 41 - 0
src/modules/love/love.cpp

@@ -21,6 +21,7 @@
 // LOVE
 #include "common/config.h"
 #include "common/version.h"
+#include "common/deprecation.h"
 #include "common/runtime.h"
 #include "common/wrap_Data.h"
 
@@ -258,6 +259,25 @@ static int w__setGammaCorrect(lua_State *L)
 	return 0;
 }
 
+static int w_love_setDeprecationOutput(lua_State *L)
+{
+	bool enable = love::luax_checkboolean(L, 1);
+	love::setDeprecationOutputEnabled(enable);
+	return 0;
+}
+
+static int w_love_hasDeprecationOutput(lua_State *L)
+{
+	love::luax_pushboolean(L, love::isDeprecationOutputEnabled());
+	return 1;
+}
+
+static int w_deprecation__gc(lua_State *)
+{
+	love::deinitDeprecation();
+	return 0;
+}
+
 int luaopen_love(lua_State *L)
 {
 	love::luax_insistpinnedthread(L);
@@ -324,6 +344,27 @@ int luaopen_love(lua_State *L)
 #endif
 	lua_setfield(L, -2, "_os");
 
+	{
+		love::initDeprecation();
+
+		// Any old data that we can attach a metatable to, for __gc. We want to
+		// call deinitDeprecation when love is garbage collected.
+		lua_newuserdata(L, sizeof(int));
+
+		luaL_newmetatable(L, "love_deprecation");
+		lua_pushcfunction(L, w_deprecation__gc);
+		lua_setfield(L, -2, "__gc");
+		lua_setmetatable(L, -2);
+
+		lua_setfield(L, -2, "_deprecation");
+
+		lua_pushcfunction(L, w_love_setDeprecationOutput);
+		lua_setfield(L, -2, "setDeprecationOutput");
+
+		lua_pushcfunction(L, w_love_hasDeprecationOutput);
+		lua_setfield(L, -2, "hasDeprecationOutput");
+	}
+
 	// Preload module loaders.
 	for (int i = 0; modules[i].name != nullptr; i++)
 		love::luax_preload(L, modules[i].func, modules[i].name);

+ 2 - 0
src/scripts/boot.lua

@@ -315,6 +315,8 @@ function love.boot()
 
 	love.filesystem.setFused(is_fused_game)
 
+	love.setDeprecationOutput(not love.filesystem.isFused())
+
 	local identity = ""
 	if not can_has_game and o.game.set and o.game.arg[1] then
 		local nouri = o.game.arg[1]

+ 4 - 0
src/scripts/boot.lua.h

@@ -578,6 +578,10 @@ const unsigned char boot_lua[] =
 	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, 
 	0x65, 0x74, 0x46, 0x75, 0x73, 0x65, 0x64, 0x28, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, 
 	0x61, 0x6d, 0x65, 0x29, 0x0a,
+	0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 
+	0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x28, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 
+	0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x46, 0x75, 0x73, 0x65, 0x64, 
+	0x28, 0x29, 0x29, 0x0a,
 	0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 
 	0x22, 0x22, 0x0a,
 	0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61,