Skip to content
This repository was archived by the owner on Aug 27, 2023. It is now read-only.

Commit 544bb34

Browse files
committed
Solar2D plugin changes for 0.9.2
1 parent ee8be99 commit 544bb34

5 files changed

Lines changed: 707 additions & 60 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// LuaSwiftParsing.swift
3+
// AppMakerProfessional-iOS
4+
//
5+
// Created by Joseph Hinkle on 6/6/22.
6+
//
7+
8+
#if MAIN_APP
9+
import LuaSwiftBindings
10+
11+
12+
internal extension Table {
13+
func get<T>(key: String) -> T? {
14+
self[key] as? T
15+
}
16+
func get(key: String) -> Int? {
17+
if let int = (self[key] as? Number)?.toInteger() {
18+
return Int(int)
19+
}
20+
return nil
21+
}
22+
}
23+
24+
protocol ConvertibleToLuaCodeString {
25+
var asLuaCodeString: String { get }
26+
}
27+
28+
extension Optional: ConvertibleToLuaCodeString where Wrapped: ConvertibleToLuaCodeString {
29+
var asLuaCodeString: String {
30+
if let self = self {
31+
return self.asLuaCodeString
32+
}
33+
return "nil"
34+
}
35+
}
36+
37+
extension Int: ConvertibleToLuaCodeString {
38+
var asLuaCodeString: String {
39+
return "\(self)"
40+
}
41+
}
42+
43+
extension String: ConvertibleToLuaCodeString {
44+
var asLuaCodeString: String {
45+
return "\"\(self)\""
46+
}
47+
}
48+
49+
50+
51+
52+
53+
#endif

src/Solar2D/Sources/Solar2D.swift

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Foundation
66
#if MAIN_APP
77
import AppMakerCore
8+
import LuaSwiftBindings
89
#elseif COMPANION
910
import AppMakerCompanionCore
1011
#endif
@@ -146,11 +147,211 @@ final class Solar2DBuildSystem: AppMakerBuildSystemImplementation {
146147
try? FileManager.default.removeItem(at: product.buildProductPath)
147148
let coronaSourceFolder = self.entryPathUrl.deletingLastPathComponent()
148149
try? FileManager.default.createDirectory(at: product.buildProductPath.deletingLastPathComponent(), withIntermediateDirectories: true)
150+
let mainLuaFile: URL = product.buildProductPath.appendingPathComponent("main.lua")
151+
let configLuaFile: URL = product.buildProductPath.appendingPathComponent("config.lua")
149152
do {
153+
// copy all source file
150154
try FileManager.default.copyItem(
151155
at: coronaSourceFolder,
152156
to: product.buildProductPath
153157
)
158+
159+
let configLuaInfo: ConfigLuaInfo
160+
if FileManager.default.fileExists(atPath: configLuaFile.path) {
161+
// read config.lua file
162+
func getConfigLuaInfo() throws -> ConfigLuaInfo {
163+
let vm = LuaSwiftBindings.VirtualMachine()
164+
_ = vm.eval(try String(contentsOf: configLuaFile))
165+
return ConfigLuaInfo(globalTable: vm.globals)
166+
}
167+
configLuaInfo = try getConfigLuaInfo()
168+
169+
// delete given config.lua file
170+
try FileManager.default.removeItem(at: configLuaFile)
171+
172+
// put in fake config.lua file
173+
try """
174+
application =
175+
{
176+
content =
177+
{
178+
scale = "adaptive",
179+
fps = \(configLuaInfo.fps.rawValue),
180+
}
181+
}
182+
""".write(to: configLuaFile, atomically: true, encoding: .utf8)
183+
} else {
184+
configLuaInfo = ConfigLuaInfo()
185+
}
186+
187+
188+
// rename main.lua and create fake main.lua to implement hooks and sandboxing
189+
try FileManager.default.moveItem(
190+
at: mainLuaFile,
191+
to: product.buildProductPath.appendingPathComponent("_original_main.lua")
192+
)
193+
194+
// create fake main.lua
195+
try """
196+
-- encode config.lua information here
197+
local configLuaDetails = \(configLuaInfo.asLuaTableString)
198+
199+
-- call this function to communicate with Solar2DSwiftUIViewDelegate
200+
local function sendToSwift(eventName, args)
201+
Runtime:dispatchEvent( { name="coronaView", eventName=eventName, args=args, isForAppMaker=true } )
202+
end
203+
204+
-- send prints to App Maker console
205+
_G.print = function(...)
206+
local args = {...}
207+
local strings = {}
208+
for _, arg in ipairs(args) do
209+
strings[#strings+1] = tostring(arg)
210+
end
211+
sendToSwift("print", strings)
212+
end
213+
214+
-- flag ensures that App Maker successfully communicates essential information about
215+
-- where we are running before we continue into the actual main.lua code
216+
local establishedCommunicationWithHostAppMakerProcess = false
217+
218+
-- save original CoronaCard content size values to the config table for App Maker to read
219+
configLuaDetails["original_actualContentWidth"] = display.actualContentWidth
220+
configLuaDetails["original_actualContentHeight"] = display.actualContentHeight
221+
222+
-- fake display object
223+
local displayOverwrites = {}
224+
local oldDisplay = display
225+
display = {}
226+
setmetatable(display, {
227+
__index = function(table, key)
228+
local result = displayOverwrites[key]
229+
if result == nil then
230+
return oldDisplay[key]
231+
else
232+
return result
233+
end
234+
end,
235+
})
236+
237+
-- App Maker will always communicate to the Solar2D CoronaCard view via this listener
238+
local function handleAppMaker( event )
239+
if ( event.eventName == "containingWindowNewSize" ) then
240+
-- todo
241+
-- local imageSuffix = event.imageSuffix
242+
243+
-- standard solar2d screen size values
244+
local actualContentWidth = event.actualContentWidth
245+
local actualContentHeight = event.actualContentHeight
246+
local contentWidth = event.contentWidth
247+
local contentHeight = event.contentHeight
248+
local contentScaleX = event.contentScaleX
249+
local contentScaleY = event.contentScaleY
250+
local pixelWidth = event.pixelWidth
251+
local pixelHeight = event.pixelHeight
252+
local topStatusBarContentHeight = event.topStatusBarContentHeight
253+
local bottomStatusBarContentHeight = event.bottomStatusBarContentHeight
254+
255+
-- simplified representation of some screen properties
256+
local insetT = event.insetT
257+
local insetL = event.insetL
258+
local insetB = event.insetB
259+
local insetR = event.insetR
260+
261+
-- derrived values
262+
local contentCenterX = contentWidth * .5
263+
local contentCenterY = contentHeight * .5
264+
local screenOriginX = (contentWidth - actualContentWidth) * .5
265+
local screenOriginY = (contentHeight - actualContentHeight) * .5
266+
local safeActualContentWidth = actualContentWidth - insetL - insetR
267+
local safeActualContentHeight = actualContentHeight - insetT - insetB
268+
local safeScreenOriginX = screenOriginX + insetL
269+
local safeScreenOriginY = screenOriginY + insetT
270+
local statusBarHeight = topStatusBarContentHeight -- note, this might actually be slightly different from topStatusBarContentHeight, but it doesn't seem worth the time figuring out exactly what that difference is
271+
local getSafeAreaInsets = function()
272+
return insetT, insetL, insetB, insetR
273+
end
274+
local viewableContentWidth = math.min(contentWidth, actualContentWidth)
275+
local viewableContentHeight = math.min(contentHeight, actualContentHeight)
276+
277+
-- hack the global stage display object to fix properly
278+
local _hackOffsetX = event._hackOffsetX
279+
local _hackOffsetY = event._hackOffsetY
280+
local _hackScaleX = event._hackScaleX
281+
local _hackScaleY = event._hackScaleY
282+
283+
-- override values in global display table
284+
displayOverwrites.actualContentWidth = actualContentWidth
285+
displayOverwrites.actualContentHeight = actualContentHeight
286+
displayOverwrites.contentWidth = contentWidth
287+
displayOverwrites.contentHeight = contentHeight
288+
displayOverwrites.contentScaleX = contentScaleX
289+
displayOverwrites.contentScaleY = contentScaleY
290+
displayOverwrites.pixelWidth = pixelWidth
291+
displayOverwrites.pixelHeight = pixelHeight
292+
displayOverwrites.topStatusBarContentHeight = topStatusBarContentHeight
293+
displayOverwrites.bottomStatusBarContentHeight = bottomStatusBarContentHeight
294+
displayOverwrites.viewableContentWidth = viewableContentWidth
295+
displayOverwrites.viewableContentHeight = viewableContentHeight
296+
displayOverwrites.contentCenterX = contentCenterX
297+
displayOverwrites.contentCenterY = contentCenterY
298+
displayOverwrites.safeActualContentWidth = safeActualContentWidth
299+
displayOverwrites.safeActualContentHeight = safeActualContentHeight
300+
displayOverwrites.safeScreenOriginX = safeScreenOriginX
301+
displayOverwrites.safeScreenOriginY = safeScreenOriginY
302+
displayOverwrites.screenOriginX = screenOriginX
303+
displayOverwrites.screenOriginY = screenOriginY
304+
displayOverwrites.statusBarHeight = statusBarHeight
305+
displayOverwrites.getSafeAreaInsets = getSafeAreaInsets
306+
307+
-- apply hack to stage object
308+
oldDisplay.currentStage.x = _hackOffsetX * _hackScaleX
309+
oldDisplay.currentStage.y = _hackOffsetY * _hackScaleY
310+
oldDisplay.currentStage.xScale = _hackScaleX
311+
oldDisplay.currentStage.yScale = _hackScaleY
312+
313+
if establishedCommunicationWithHostAppMakerProcess then
314+
-- not the first time called, call the resize listener
315+
Runtime:dispatchEvent( {name="resize"} )
316+
else
317+
-- first time called
318+
establishedCommunicationWithHostAppMakerProcess = true
319+
end
320+
end
321+
end
322+
Runtime:addEventListener( "appMaker", handleAppMaker )
323+
324+
-- intercept addEventListener
325+
local oldAddEventListener = Runtime.addEventListener
326+
function Runtime.addEventListener(...)
327+
local args = {...}
328+
if #args > 1 then
329+
local eventName = args[2]
330+
if eventName == "accelerometer" then
331+
-- the accelerometer is correctly broken for CoronaCards
332+
-- https://forums.solar2d.com/t/gyroscope-and-accelerometer-not-working-on-ios/331006
333+
print("⚠️: accelerometer does not work with CoronaCards")
334+
return
335+
end
336+
end
337+
return oldAddEventListener(unpack(args))
338+
end
339+
340+
-- send embedded config lua details
341+
sendToSwift("configLuaDetails", configLuaDetails)
342+
343+
-- actual main.lua
344+
if establishedCommunicationWithHostAppMakerProcess then
345+
local status, err = pcall(function() require("_original_main") end)
346+
if status == false then
347+
sendToSwift("error", {message=tostring(err)})
348+
end
349+
else
350+
print("❌ failed to establish communication between Solar2D (CoronaCards) and App Maker IDE")
351+
end
352+
""".write(to: mainLuaFile, atomically: true, encoding: .utf8)
353+
354+
// done
154355
return .success(Void())
155356
} catch {
156357
return .failure(error)
@@ -172,5 +373,98 @@ final class Solar2DBuildSystem: AppMakerBuildSystemImplementation {
172373
#endif
173374
}
174375
}
376+
377+
378+
struct ConfigLuaInfo {
379+
let width: Int?
380+
let height: Int?
381+
let scale: ScaleMode
382+
let fps: FPS
383+
384+
// for when app is running
385+
let original_actualContentWidth: Double?
386+
let original_actualContentHeight: Double?
387+
388+
var desiredSize: CGSize? {
389+
if let width = width, let height = height {
390+
return CGSize(width: width, height: height)
391+
}
392+
return nil
393+
}
394+
395+
init() {
396+
self.scale = .none
397+
self.width = nil
398+
self.height = nil
399+
self.fps = ._30
400+
self.original_actualContentWidth = nil
401+
self.original_actualContentHeight = nil
402+
}
403+
init(globalTable: Table) {
404+
guard let application: Table = globalTable.get(key: "application"),
405+
let content: Table = application.get(key: "content") else {
406+
self = .init()
407+
return
408+
}
409+
self.width = content.get(key: "width")
410+
self.height = content.get(key: "height")
411+
if let scaleModeStr: String = content.get(key: "scale") {
412+
self.scale = .init(rawValue: scaleModeStr.lowercased()) ?? .none
413+
} else {
414+
self.scale = .none
415+
}
416+
if let fpsInt: Int = content.get(key: "fps") {
417+
self.fps = .init(rawValue: fpsInt) ?? ._30
418+
} else {
419+
self.fps = ._30
420+
}
421+
self.original_actualContentWidth = nil
422+
self.original_actualContentHeight = nil
423+
}
424+
init?(args: Dictionary<AnyHashable, Any>) {
425+
self.width = (args["width"] as? NSNumber)?.intValue
426+
self.height = (args["height"] as? NSNumber)?.intValue
427+
if let scaleModeStr: String = args["scale"] as? String {
428+
self.scale = .init(rawValue: scaleModeStr.lowercased()) ?? .none
429+
} else {
430+
self.scale = .none
431+
}
432+
if let fpsInt: NSNumber = args["fps"] as? NSNumber {
433+
self.fps = .init(rawValue: fpsInt.intValue) ?? ._30
434+
} else {
435+
self.fps = ._30
436+
}
437+
guard let original_actualContentWidth: Double = (args["original_actualContentWidth"] as? NSNumber)?.doubleValue,
438+
let original_actualContentHeight: Double = (args["original_actualContentHeight"] as? NSNumber)?.doubleValue else {
439+
return nil
440+
}
441+
self.original_actualContentWidth = original_actualContentWidth
442+
self.original_actualContentHeight = original_actualContentHeight
443+
}
444+
445+
enum ScaleMode: String {
446+
case none = ""
447+
case zoomEven = "zoomeven"
448+
case zoomStretch = "zoomstretch"
449+
case letterbox = "letterbox"
450+
case adaptive = "adaptive"
451+
}
452+
453+
enum FPS: Int {
454+
case _30 = 30
455+
case _60 = 60
456+
}
457+
458+
var asLuaTableString: String {
459+
return """
460+
{
461+
width = \(self.width.asLuaCodeString),
462+
height = \(self.height.asLuaCodeString),
463+
fps = \(self.fps.rawValue.asLuaCodeString),
464+
scale = \(self.scale.rawValue.asLuaCodeString),
465+
}
466+
"""
467+
}
468+
}
175469
#endif
176470

0 commit comments

Comments
 (0)