android-release: $(shell nitls -M src/action_nitro.nit -m gamnit::android19 -m src/touch_ui.nit) pre-build android/res/
nitc src/action_nitro.nit -m gamnit::android19 -m src/touch_ui.nit -o bin/action_nitro.apk --release
+ios: bin/action_nitro.app
+bin/action_nitro.app: $(shell nitls -M src/action_nitro.nit -m ios -m src/touch_ui.nit) pre-build
+ nitc src/action_nitro.nit -m ios -m src/touch_ui.nit -o $@ --compile-dir nit_compile
+
src/gen/texts.nit: art/texts.svg
make -C ../inkscape_tools/
../inkscape_tools/bin/svg_to_png_and_nit art/texts.svg -a assets/ -s src/gen/ -x 2.0 -g
--- /dev/null
+/nit_compile
all: bin/asteronits
-bin/asteronits: $(shell ${NITLS} -M src/asteronits.nit linux) pre-build
+bin/asteronits: $(shell ${NITLS} -M src/asteronits.nit -m linux) pre-build
${NITC} src/asteronits.nit -m linux -o $@
bin/texture_atlas_parser: ../../lib/gamnit/texture_atlas_parser.nit
# Android
android: bin/asteronits.apk
-bin/asteronits.apk: $(shell ${NITLS} -M src/asteronits.nit android) android/res/ pre-build
+bin/asteronits.apk: $(shell ${NITLS} -M src/asteronits.nit -m android) android/res/ pre-build
${NITC} src/android.nit -m android -o $@
-android-release: $(shell ${NITLS} -M src/asteronits.nit android) android/res/ pre-build
+android-release: $(shell ${NITLS} -M src/asteronits.nit -m android) android/res/ pre-build
${NITC} src/android.nit -m android -o bin/asteronits.apk --release
android/res/: art/icon.svg
make -C ../inkscape_tools/
../inkscape_tools/bin/svg_to_icons --out android/res --android art/icon.svg
+
+# ---
+# iOS
+
+ios: bin/asteronits.app
+bin/asteronits.app: $(shell ${NITLS} -M src/asteronits.nit -m ios -m src/touch_ui.nit) pre-build ios/AppIcon.appiconset/Contents.json
+ ${NITC} src/asteronits.nit -m ios -m src/touch_ui.nit -o $@ --compile-dir nit_compile
+
+ios/AppIcon.appiconset/Contents.json: art/icon.svg
+ mkdir -p ios
+ ../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --ios --out ios/AppIcon.appiconset/
# Android
android: bin/model_viewer.apk
-bin/model_viewer.apk: $(shell ${NITLS} -M src/model_viewer.nit android) android/res/
+bin/model_viewer.apk: $(shell ${NITLS} -M src/model_viewer.nit -m android) android/res/
${NITC} src/model_viewer.nit -m android -o $@
-android-release: $(shell ${NITLS} -M src/model_viewer.nit android) android/res/
+android-release: $(shell ${NITLS} -M src/model_viewer.nit -m android) android/res/
${NITC} src/model_viewer.nit -m android -o bin/model_viewer.apk --release
android/res/: art/icon.png
mkdir -p android/libs
curl --progress-bar -o android/libs/cardboard.jar \
https://raw.githubusercontent.com/googlevr/gvr-android-sdk/e226f15c/CardboardSample/libs/cardboard.jar
+
+# ---
+# iOS
+
+ios: bin/model_viewer.app
+bin/model_viewer.app: $(shell ${NITLS} -M src/model_viewer.nit -m ios)
+ ${NITC} src/model_viewer.nit -m ios -o $@ --compile-dir nit_compile
# Platform variations
import linux::audio is conditional(linux)
import android::audio is conditional(android)
+import ios::audio is conditional(ios)
# Abstraction of a playable Audio
abstract class PlayableAudio
# System configuration
To compile the _gamnit_ apps packaged with the Nit repository on GNU/Linux you need to install the dev version of a few libraries and some tools.
-Under Debian 8.2, this command should install everything needed:
+On Debian 8.2, this command should install everything needed:
~~~
apt-get install libgles2-mesa-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev inkscape
~~~
-Under Windows 64 bits, using msys2, you can install the required packages with:
+On Windows 64 bits, using msys2, you can install the required packages with:
~~~
pacman -S mingw-w64-x86_64-angleproject-git mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer
~~~
+While macOS isn't supported, it can create iOS apps.
+You need to install and setup Xcode, and you may install the GLSL shader validation tool via `brew`:
+
+~~~
+brew install glslang
+~~~
+
# Services by submodules
_gamnit_ is modular, different services of the framework are available through different submodules:
end
# Take down, bring back default values
- glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
+ bind_screen_framebuffer shadow_context.screen_framebuffer
glColorMask(true, true, true, true)
end
assert gl_error == gl_NO_ERROR else print_error gl_error
resize(display, shadow_resolution)
- assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
# Array buffer
buffer_array = glGenBuffers(1).first
import display_linux is conditional(linux)
import display_android is conditional(android)
+import display_ios is conditional(ios)
# Should Gamnit be more verbose?
fun debug_gamnit: Bool do return false
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Gamnit display implementation for iOS
+module display_ios
+
+import ios
+import ios::glkit
+intrude import ios::assets
+intrude import textures
+
+in "ObjC" `{
+ #import <GLKit/GLKit.h>
+ #import <OpenGLES/ES2/gl.h>
+`}
+
+redef class GamnitDisplay
+
+ redef var width = 200
+ redef var height = 300
+
+ # Underlying GLKit game controller and view
+ var glk_view: NitGLKView is noautoinit
+
+ redef fun setup
+ do
+ var view = new GamnitGLKView
+ view.multiple_touch_enabled = true
+ self.glk_view = view
+ self.width = view.drawable_width
+ self.height = view.drawable_height
+ end
+end
+
+# View controller implemented by gamnit
+class GamnitGLKView
+ super NitGLKView
+end
+
+redef class TextureAsset
+ redef fun load_from_platform
+ do
+ var error = glGetError
+ assert error == gl_NO_ERROR else print_error error
+
+ # Find file
+ var ns_path = ("assets"/path).to_nsstring
+ var path_in_bundle = asset_path(ns_path)
+ if path_in_bundle.address_is_null then
+ self.error = new Error("Texture at '{path}' not found")
+ return
+ end
+
+ # Load texture
+ var glk_texture = glkit_load(path_in_bundle, premultiply_alpha)
+ if glk_texture.address_is_null then
+ self.error = new Error("Failed to load texture at '{self.path}'")
+ return
+ end
+
+ gl_texture = glk_texture.name
+ width = glk_texture.width.to_f
+ height = glk_texture.height.to_f
+ loaded = true
+
+ error = glGetError
+ assert error == gl_NO_ERROR
+ end
+
+ # Load image at `path` with GLKit services
+ private fun glkit_load(path: NSString, premultiply: Bool): GLKTextureInfo
+ in "ObjC" `{
+
+ // The premultiplication flag has been inverted between iOS 9 and 10
+ NSNumber *premultiply_opt;
+ NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: @"10.0.0" options: NSNumericSearch];
+ if (order == NSOrderedSame || order == NSOrderedDescending) {
+ // >= 10
+ premultiply_opt = premultiply? @NO: @YES;
+ } else {
+ // < 10
+ premultiply_opt = premultiply? @YES: @NO;
+ }
+
+ NSDictionary *options = @{GLKTextureLoaderApplyPremultiplication: premultiply_opt};
+ NSError *error;
+ GLKTextureInfo *spriteTexture = [GLKTextureLoader textureWithContentsOfFile: path options: options error: &error];
+ if (error != nil) NSLog(@"Failed to load texture: %@", [error localizedDescription]); // TODO return details to Nit
+
+ return spriteTexture;
+ `}
+end
+
+private extern class GLKTextureInfo in "ObjC" `{ GLKTextureInfo * `}
+ super NSObject
+
+ fun name: Int in "ObjC" `{ return self.name; `}
+ fun width: Int in "ObjC" `{ return self.width; `}
+ fun height: Int in "ObjC" `{ return self.height; `}
+end
private var perf_clock_dynamic_resolution = new Clock is lazy
- redef fun create_scene
+ # Real screen framebuffer
+ private var screen_framebuffer_cache: Int = -1
+
+ # Real screen framebuffer name
+ fun screen_framebuffer: Int
+ do
+ var cache = screen_framebuffer_cache
+ if cache != -1 then return cache
+
+ cache = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
+ self.screen_framebuffer_cache = cache
+ return cache
+ end
+
+ redef fun create_gamnit
do
super
program.compile_and_link
var error = program.error
assert error == null else print_error error
+
+ dynamic_context_cache = null
end
redef fun on_resize(display)
do
- dynamic_context.resize(display, max_dynamic_resolution_ratio)
+ if dynamic_context_cache != null then dynamic_context.resize(display, max_dynamic_resolution_ratio)
super
end
if dynamic_resolution_ratio == 1.0 then
# Draw directly to the screen framebuffer
- glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.screen_framebuffer)
+ bind_screen_framebuffer screen_framebuffer
glViewport(0, 0, display.width, display.height)
glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
var ratio = dynamic_resolution_ratio
ratio = ratio.clamp(min_dynamic_resolution_ratio, max_dynamic_resolution_ratio)
- glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.screen_framebuffer)
+ bind_screen_framebuffer screen_framebuffer
glBindBuffer(gl_ARRAY_BUFFER, dynamic_context.buffer_array)
glViewport(0, 0, display.width, display.height)
glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
end
# Framebuffer and texture for dynamic resolution intermediate drawing
- private var dynamic_context: DynamicContext = create_dynamic_context is lazy
+ private fun dynamic_context: DynamicContext
+ do
+ var cache = dynamic_context_cache
+ if cache != null then return cache
+
+ cache = create_dynamic_context
+ dynamic_context_cache = cache
+ return cache
+ end
+
+ private var dynamic_context_cache: nullable DynamicContext = null
private fun create_dynamic_context: DynamicContext
do
# Handles to reused GL buffers and texture
private class DynamicContext
- # Real screen framebuffer
- var screen_framebuffer: Int = -1
-
# Dynamic screen framebuffer
var dynamic_framebuffer: Int = -1
do
# TODO enable antialiasing.
- # Set aside the real screen framebuffer name
- var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
- self.screen_framebuffer = screen_framebuffer
-
# Framebuffer
var framebuffer = glGenFramebuffers(1).first
glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
do
super
- # Clean up
- simple_2d_program.delete
-
# Close gamnit
var display = display
if display != null then display.close
import gamnit_android is conditional(android)
import gamnit_linux is conditional(linux)
+import gamnit_ios is conditional(ios)
+import input_ios is conditional(ios)
redef class App
# The framework handles resizing the viewport automatically.
fun on_resize(display: GamnitDisplay) do end
end
+
+# Portable indirection to `glBindFramebuffer(gl_FRAMEBUFFER, fbo)`
+#
+# This is implemented differently on iOS.
+fun bind_screen_framebuffer(fbo: Int) do glBindFramebuffer(gl_FRAMEBUFFER, fbo)
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Support services for gamnit on iOS
+module gamnit_ios
+
+import ios
+import gamnit
+
+import ios::assets
+
+redef class App
+ redef fun did_finish_launching_with_options
+ do
+ create_gamnit
+ create_scene
+ return super
+ end
+
+ # Disable the game loop to rely on the GLKView callbacks on each frame instead
+ redef fun run do end
+
+ private fun frame_full_indirect do frame_full
+end
+
+redef class GamnitGLKView
+ redef fun update do app.frame_full_indirect
+end
+
+redef fun bind_screen_framebuffer(fbo)
+do
+ var display = app.display
+ assert display != null
+ display.glk_view.bind_drawable
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Gamnit event support for iOS
+module input_ios
+
+intrude import ios::glkit
+import display_ios
+import gamnit_ios
+
+# Pointer/touch event on iOS
+class GamnitIOSPointerEvent
+ super PointerEvent
+
+ private var native: UIEvent
+
+ private var native_touch: UITouch
+
+ private var content_scale_factor: Float
+
+ redef fun x do return native_touch.x * content_scale_factor
+
+ redef fun y do return native_touch.y * content_scale_factor
+
+ redef var pressed
+
+ redef var is_move
+
+ redef var pointer_id = native_touch.to_i is lazy
+end
+
+redef class NitGLKView
+
+ redef var content_scale_factor = super is lazy
+
+ redef fun touches_began(touches, event)
+ do app.accept_event(new GamnitIOSPointerEvent(event, touches.any_object, content_scale_factor, true, false))
+
+ redef fun touches_moved(touches, event)
+ do app.accept_event(new GamnitIOSPointerEvent(event, touches.any_object, content_scale_factor, true, true))
+
+ redef fun touches_ended(touches, event)
+ do app.accept_event(new GamnitIOSPointerEvent(event, touches.any_object, content_scale_factor, false, false))
+
+ # TODO handle cancel
+ #redef fun touches_cancelled(touches_event) do
+ #do app.accept_event(new GamnitIOSPointerEvent(event, false, false))
+end
intrude import c
in "C Header" `{
+#ifdef __APPLE__
+ #include <OpenGLES/ES2/gl.h>
+#else
#include <GLES2/gl2.h>
+#endif
`}
# OpenGL ES program to which we attach shaders
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Implementation of `app::assets`
+module assets
+
+import cocoa
+import app::assets
+
+redef class TextAsset
+ redef fun load
+ do
+ # Find file
+ var ns_path = ("assets"/path).to_nsstring
+ var path_in_bundle = asset_path(ns_path)
+ if path_in_bundle.address_is_null then
+ self.error = new Error("TextAsset at '{path}' not found")
+ self.to_s = ""
+ return ""
+ end
+
+ # Load content
+ var text = asset_content(path_in_bundle)
+ if text.address_is_null then
+ self.error = new Error("Failed to read content of TextAsset at '{path}'")
+ self.to_s = ""
+ return ""
+ end
+
+ return text.to_s
+ end
+end
+
+private fun asset_path(path: NSString): NSString in "ObjC" `{
+ return [[NSBundle mainBundle] pathForResource:path ofType:nil];
+`}
+
+private fun asset_url(path: NSString): NSObject in "ObjC" `{
+ return [[NSBundle mainBundle] URLForResource:path withExtension:nil];
+`}
+
+private fun asset_content(path: NSString): NSString in "ObjC" `{
+ return [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
+`}
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# iOS implementation of `app::audio` using `AVAudioPlayer`
+module audio
+
+import app::audio
+intrude import ios::assets
+
+in "ObjC Header" `{
+ #import <AVFoundation/AVFoundation.h>
+`}
+
+redef class PlayableAudio
+
+ redef var error = null
+
+ private var native: nullable AVAudioPlayer is lazy do
+
+ # Find file
+ var ns_path = ("assets"/path).to_nsstring
+ var url_in_bundle = asset_url(ns_path)
+ if url_in_bundle.address_is_null then
+ self.error = new Error("Sound at '{path}' not found")
+ return null
+ end
+
+ var player = new AVAudioPlayer.contents_of(url_in_bundle)
+ # TODO set delegate to get further errors
+ if player.address_is_null then
+ self.error = new Error("Sound at '{path}' failed to load")
+ return null
+ end
+
+ player.prepare_to_play
+
+ return player
+ end
+
+ redef fun load do native # For lazy loading
+
+ redef fun play
+ do
+ var native = native
+ if native != null then native.play_and_repare_async
+ end
+
+ # Free native resources
+ fun destroy
+ do
+ var native = native
+ if native != null then native.release
+ end
+end
+
+# Audio player playing audio from a file or from memory
+private extern class AVAudioPlayer in "ObjC" `{ AVAudioPlayer *`}
+ super NSObject
+
+ new contents_of(url: NSObject) in "ObjC" `{
+ NSError *error;
+ AVAudioPlayer *a = [[AVAudioPlayer alloc] initWithContentsOfURL:(NSURL*)url error:&error];
+ if (error != nil) {
+ NSLog(@"Failed to load sound: %@", [error localizedDescription]);
+ return NULL;
+ }
+
+ return (__bridge AVAudioPlayer*)CFBridgingRetain(a);
+ `}
+
+ fun play in "ObjC" `{ [self play]; `}
+
+ fun prepare_to_play in "ObjC" `{ [self prepareToPlay]; `}
+
+ fun play_and_repare_async in "ObjC" `{
+ dispatch_queue_t q = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
+ dispatch_async(q, ^{
+ [self play];
+ [self prepareToPlay];
+ });
+ `}
+
+ fun release in "ObjC" `{ CFBridgingRelease((__bridge void*)self); `}
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# GLKit services to create an OpenGL ES context on iOS
+module glkit
+
+#import glesv2
+import ios
+
+in "ObjC Header" `{
+ #import <GLKit/GLKit.h>
+
+ // Nit controller for games
+ @interface GameViewController : GLKViewController
+
+ // Nit object receiving callbacks
+ @property void* nit_glk_view;
+ @end
+`}
+
+in "ObjC" `{
+
+ @implementation GameViewController
+
+ - (void)update
+ {
+ NitGLKView_update((NitGLKView)self.nit_glk_view);
+ }
+
+ - (UIInterfaceOrientationMask)supportedInterfaceOrientations
+ {
+ long res = NitGLKView_supported_interface_orientations((NitGLKView)self.nit_glk_view);
+ if (res == 0) return [super supportedInterfaceOrientations];
+ return (UIInterfaceOrientationMask)res;
+ }
+
+ - (BOOL)shouldAutorotate
+ {
+ return NitGLKView_should_autorotate((NitGLKView)self.nit_glk_view);
+ }
+
+ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+ {
+ NitGLKView_touches_began((NitGLKView)self.nit_glk_view, touches, event);
+ }
+
+ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+ {
+ NitGLKView_touches_moved((NitGLKView)self.nit_glk_view, touches, event);
+ }
+
+ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+ {
+ NitGLKView_touches_ended((NitGLKView)self.nit_glk_view, touches, event);
+ }
+
+ - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+ {
+ NitGLKView_touches_cancelled((NitGLKView)self.nit_glk_view, touches, event);
+ }
+
+ - (void)viewWillTransitionToSize:(CGSize)size
+ withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
+ NitGLKView_view_will_transition_to_size((NitGLKView)self.nit_glk_view, size.width, size.height);
+ }
+ @end
+`}
+
+# Wrapper for both an Objective-C `GLKViewController` and its `GLKView`
+private extern class NativeGLKViewController in "ObjC" `{ GLKViewController * `}
+ super NSObject
+
+ fun content_scale_factor: Float in "ObjC" `{ return self.view.contentScaleFactor; `}
+ fun drawable_width: Int in "ObjC" `{ return ((GLKView*)self.view).drawableWidth; `}
+ fun drawable_height: Int in "ObjC" `{ return ((GLKView*)self.view).drawableHeight; `}
+ fun bind_drawable in "ObjC" `{ [((GLKView*)self.view) bindDrawable]; `}
+
+ fun multiple_touch_enabled: Bool in "ObjC" `{ return [self.view isMultipleTouchEnabled]; `}
+ fun multiple_touch_enabled=(val: Bool) in "ObjC" `{ return [self.view setMultipleTouchEnabled: val]; `}
+end
+
+# OpenGL view controller
+class NitGLKView
+ private var native: NativeGLKViewController = setup(app.app_delegate)
+
+ # Scale factor from logical coordinate space to the device coordinate space
+ fun content_scale_factor: Float do return native.content_scale_factor
+
+ # Width of the underlying framebuffer
+ fun drawable_width: Int do return native.drawable_width
+
+ # Height of the underlying framebuffer
+ fun drawable_height: Int do return native.drawable_height
+
+ # Bind the view framebuffer
+ fun bind_drawable do native.bind_drawable
+
+ private fun setup(app_delegate: AppDelegate): NativeGLKViewController
+ import touches_began, touches_moved, touches_ended, touches_cancelled,
+ update, should_autorotate, supported_interface_orientations,
+ view_will_transition_to_size in "ObjC" `{
+
+ app_delegate.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
+ app_delegate.window.backgroundColor = [UIColor whiteColor]; // TODO make configurable
+
+ // Create EAGL context and view
+ EAGLContext * context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
+ GLKView *view = [[GLKView alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
+ view.context = context;
+
+ // Ask for antialiasing
+ view.drawableMultisample = GLKViewDrawableMultisample4X;
+ view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
+
+ GameViewController *cont = [[GameViewController alloc] init];
+ cont.view = view;
+
+ // Setup callbacks
+ NitGLKView_incr_ref(self);
+ cont.nit_glk_view = self;
+
+ // Make our controller the root
+ view.delegate = cont;
+ [app_delegate.window setRootViewController: cont];
+
+ // Enable the context
+ [app_delegate.window makeKeyAndVisible];
+ [EAGLContext setCurrentContext: context];
+ [view bindDrawable];
+
+ return cont;
+ `}
+
+ # Is multi-touch supported?
+ fun multiple_touch_enabled: Bool do return native.multiple_touch_enabled
+
+ # Is multi-touch supported?
+ fun multiple_touch_enabled=(val: Bool) do native.multiple_touch_enabled = val
+
+ # Should the view auto rotate to follow the device orientation?
+ #
+ # Defaults to `true`.
+ fun should_autorotate: Bool do return true
+
+ # If `should_autorotate`, what are the supported interface orientations?
+ #
+ # Redef to return values of Objective-C `UIInterfaceOrientationMask`
+ fun supported_interface_orientations: Int do return 0
+
+ # Hook to update the view content, called once per frame
+ fun update do end
+
+ # Hook on a new touch event
+ fun touches_began(touches: NSSet_UITouch, event: UIEvent) do end
+
+ # Hook when a touch moves
+ fun touches_moved(touches: NSSet_UITouch, event: UIEvent) do end
+
+ # Hook on the end of a touch event
+ fun touches_ended(touches: NSSet_UITouch, event: UIEvent) do end
+
+ # Hook on a touch event cancellation
+ fun touches_cancelled(touches: NSSet_UITouch, event: UIEvent) do end
+
+ # Hook when size of the view is about to change to `width` by `height`
+ fun view_will_transition_to_size(width, height: Float) do end
+end
+
+# UIKit event
+extern class UIEvent in "ObjC" `{ UIEvent * `}
+ super NSObject
+end
+
+# Objective-C `NSSet` of `UITouch`
+extern class NSSet_UITouch in "ObjC" `{ NSSet<UITouch*>* `}
+ super NSObject
+
+ # Get any object of this set
+ fun any_object: UITouch in "ObjC" `{ return [self anyObject]; `}
+end
+
+# UIKit touch event
+extern class UITouch in "ObjC" `{ UITouch * `}
+
+ # X coordinate
+ fun x: Float in "ObjC" `{ return [self locationInView:self.view].x; `}
+
+ # Y coordinate
+ fun y: Float in "ObjC" `{ return [self locationInView:self.view].y; `}
+
+ # Address of this object as an integer for identity detection
+ fun to_i: Int in "ObjC" `{ return (long)self; `}
+end
// TODO protect with: #ifdef WITH_LIBGC
// We might have to add the next line to gc_chooser.c too, especially
// if we get an error like "thread not registered with GC".
- #ifdef __APPLE__
- #include "TargetConditionals.h"
- #if TARGET_OS_IPHONE == 1
- #define IOS
- #endif
- #endif
-
- #if !defined(IOS)
- #define GC_THREADS
- #include <gc.h>
- #endif
+ #define GC_THREADS
+ #include <gc.h>
`}
redef class Sys
in "C" `{
-#if defined(__MACH__) && !defined(CLOCK_REALTIME)
+#ifdef __APPLE__
+ #include <TargetConditionals.h>
+ #if defined(TARGET_OS_IPHONE) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
+ // Preserve compatibility with pre-iOS 10 devices where there is no clock_get_time.
+ #undef CLOCK_REALTIME
+ #endif
+#endif
+
+#if (defined(__MACH__) || defined(TARGET_OS_IPHONE)) && !defined(CLOCK_REALTIME)
/* OS X does not have clock_gettime, mascarade it and use clock_get_time
* cf http://stackoverflow.com/questions/11680461/monotonic-clock-on-osx
*/
#include <mach/clock.h>
#include <mach/mach.h>
+#undef CLOCK_REALTIME
+#undef CLOCK_MONOTONIC
#define CLOCK_REALTIME CALENDAR_CLOCK
#define CLOCK_MONOTONIC SYSTEM_CLOCK
-void clock_gettime(clock_t clock_name, struct timespec *ts) {
+void nit_clock_gettime(clock_t clock_name, struct timespec *ts) {
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), clock_name, &cclock);
ts->tv_sec = mts.tv_sec;
ts->tv_nsec = mts.tv_nsec;
}
+#else
+ #define nit_clock_gettime clock_gettime
#endif
`}
# Init a new Timespec from now.
new monotonic_now `{
struct timespec* tv = malloc( sizeof(struct timespec) );
- clock_gettime( CLOCK_MONOTONIC, tv );
+ nit_clock_gettime( CLOCK_MONOTONIC, tv );
return tv;
`}
# Update `self` clock.
fun update `{
- clock_gettime(CLOCK_MONOTONIC, self);
+ nit_clock_gettime(CLOCK_MONOTONIC, self);
`}
# Subtract `other` from `self`
# Smallest time frame reported by clock
private fun resolution: Timespec `{
struct timespec* tv = malloc( sizeof(struct timespec) );
-#if defined(__MACH__) && !defined(CLOCK_REALTIME)
+#if (defined(__MACH__) || defined(TARGET_OS_IPHONE)) && !defined(CLOCK_REALTIME)
clock_serv_t cclock;
int nsecs;
mach_msg_type_number_t count;
# Fetch libgc/bdwgc
-# cd to the absolute installation path
-if expr match "$0" "^/.*"; then
- install="`dirname "$0"`"
-else
- install="`pwd`/`dirname "$0"`"
-fi
-cd $install
+# cd to the installation path
+cd "`dirname "${BASH_SOURCE[0]}"`"
# Download or redownload
rm -rf bdwgc
super Platform
redef fun supports_libunwind do return false
- redef fun supports_libgc do return false
+ redef fun supports_libgc do return true
redef fun toolchain(toolcontext, compiler) do return new IOSToolchain(toolcontext, compiler)
end
redef fun default_outname do return "{super}.app"
+ private var bdwgc_dir: nullable String = null
+
# Compile C files in `ios_project_root/app_project.name`
redef fun compile_dir
do
if ios_project_root.file_exists then ios_project_root.rmdir
compile_dir.mkdir
+ # Download the libgc/bdwgc sources
+ var nit_dir = toolcontext.nit_dir or else "."
+ var share_dir = (nit_dir/"share").realpath
+ if not share_dir.file_exists then
+ print "iOS project error: Nit share directory not found, please use the environment variable NIT_DIR"
+ exit 1
+ end
+
+ var bdwgc_dir = "{share_dir}/android-bdwgc/bdwgc"
+ self.bdwgc_dir = bdwgc_dir
+ if not bdwgc_dir.file_exists then
+ toolcontext.exec_and_check(["{share_dir}/android-bdwgc/setup.sh"], "iOS project error")
+ end
+
super
end
var icons_found = false
- for path in app_files do
- var icon_dir = path / "ios" / "AppIcon.appiconset"
- if icon_dir.file_exists then
- icons_found = true
-
- # Prepare the `Assets.xcassets` folder
- var target_assets_dir = compile_dir / "Assets.xcassets"
- if not target_assets_dir.file_exists then target_assets_dir.mkdir
-
- """
+ # Prepare the `Assets.xcassets` folder
+ var target_assets_dir = compile_dir / "Assets.xcassets"
+ if not target_assets_dir.file_exists then target_assets_dir.mkdir
+ """
{
"info" : {
"version" : 1,
}
}""".write_to_file target_assets_dir / "Contents.json"
+ (compile_dir / "assets").mkdir
+
+ for path in app_files do
+
+ # Icon
+ var icon_dir = path / "ios" / "AppIcon.appiconset"
+ if icon_dir.file_exists then
+ icons_found = true
+
+
# copy the res folder to the compile dir
icon_dir = icon_dir.realpath
toolcontext.exec_and_check(["cp", "-R", icon_dir, target_assets_dir], "iOS project error")
end
- end
- # TODO Register asset files
+ # Assets
+ var assets_dir = path / "assets"
+ if assets_dir.file_exists then
+ assets_dir = assets_dir.realpath
+ toolcontext.exec_and_check(["cp", "-r", assets_dir, compile_dir], "iOS project error")
+ end
+ end
# ---
# project_folder.xcodeproj (projet meta data)
pbx.add_file new PbxFile(file.filename.basename)
end
+ # GC
+ if compiler.target_platform.supports_libgc then
+ var bdwgc_dir = bdwgc_dir
+ assert bdwgc_dir != null
+
+ pbx.cflags = "-I '{bdwgc_dir}/include/' -I '{bdwgc_dir}/libatomic_ops/src' -fno-strict-aliasing " +
+ "-DWITH_LIBGC -DNO_EXECUTE_PERMISSION -DALL_INTERIOR_POINTERS -DGC_NO_THREADS_DISCOVERY -DNO_DYLD_BIND_FULLY_IMAGE " +
+ "-DGC_DISABLE_INCREMENTAL -DGC_THREADS -DUSE_MMAP -DUSE_MUNMAP -DGC_GCJ_SUPPORT -DJAVA_FINALIZATION "
+
+ var gc_file = new PbxFile("{bdwgc_dir}/extra/gc.c")
+ gc_file.cflags = "-Wno-tautological-pointer-compare"
+ pbx.add_file gc_file
+ end
+
# Basic storyboard, mainly to have the right screen size
var launch_screen_storyboard = new LaunchScreenStoryboardTemplate
launch_screen_storyboard.title = app_project.name
# Path to `self`
var path: String
+ # Compiler flags for this source file
+ var cflags: String = "" is writable
+
# UUID for build elements
private var build_uuid: String = sys.pbx_uuid_generator.next_uuid is lazy
end
# PBX description of this file
- private fun description: Writable do return """
+ private fun description: Writable
+ do
+ var extra = ""
+ var cflags = cflags
+ if not cflags.is_empty then extra = "\nsettings = \{COMPILER_FLAGS = \"{cflags}\"; \};"
+
+ return """
{{{ref_uuid}}} /* {{{doc}}} */ = {
isa = PBXFileReference;
fileEncoding = 4;
lastKnownFileType = {{{file_type}}};
- path = {{{path}}};
- sourceTree = "<group>";
+ path = '{{{path}}}';
+ sourceTree = "<group>";{{{extra}}}
};
"""
+ end
private fun add_to_project(project: PbxprojectTemplate)
do
# Name of the project
var name: String
+ # OTHER_CFLAGS
+ var cflags = "" is writable
+
# All body/implementation source files to be compiled
private var source_files = new Array[PbxFile]
"""
add """
+ 0F4688411FDF8748004F34D4 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 0F4688401FDF8748004F34D4 /* assets */; };
0FDD07A21C6F8E0E006FF70E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0FDD07A11C6F8E0E006FF70E /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
for file in files do add file.description
add """
+ 0F4688401FDF8748004F34D4 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = {{{name}}}/assets; sourceTree = SOURCE_ROOT; };
0FDD07A11C6F8E0E006FF70E /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
/* End PBXFileReference section */
AF9F83CE1A5F0D21004B62C0 /* {{{name}}} */ = {
isa = PBXGroup;
children = (
+ 0F4688401FDF8748004F34D4 /* assets */,
"""
# Reference all known files
for file in files do add """
add """
0FDD07A21C6F8E0E006FF70E /* LaunchScreen.storyboard in Resources */,
+ 0F4688411FDF8748004F34D4 /* assets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = {{{name}}}/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ OTHER_CFLAGS = "{{{cflags.escape_to_c}}}";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = {{{name}}}/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ OTHER_CFLAGS = "{{{cflags.escape_to_c}}}";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
ui_test
readline
postgres
+test_nitcorn
+test_annot_pkgconfig
+test_glsl_validation
out/test_platform_ios.bin/PkgInfo
out/test_platform_ios.bin/_CodeSignature
out/test_platform_ios.bin/_CodeSignature/CodeResources
+out/test_platform_ios.bin/assets
out/test_platform_ios.bin/test_platform_ios
#!/bin/sh
-./listfull.sh | xargs -E '' -x -- ./tests.sh "$@"
+./listfull.sh | xargs -E '' -- ./tests.sh "$@"
# Set lang do default to avoid failed tests because of locale
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
+if uname | grep Darwin 1>/dev/null 2>&1; then
+ export LANG=en_US.UTF-8
+ export LC_ALL=en_US.UTF-8
+fi
+
export NIT_TESTING=true
# Use the pid as a collision prevention
export NIT_TESTING_ID=$$
# Get the first Java lib available
if which_java=$(which javac 2>/dev/null); then
- JAVA_HOME=$(dirname $(dirname $(readlink -f "$which_java")))
+
+ if sh -c "readlink -f ." 1>/dev/null 2>&1; then
+ READLINK="readlink -f"
+ else
+ # Darwin?
+ READLINK="readlink"
+ fi
+ JAVA_HOME=$(dirname $(dirname $($READLINK "$which_java")))
shopt -s nullglob
paths=`echo $JAVA_HOME/jre/lib/*/{client,server}/libjvm.so`
- paths=($paths)
- JNI_LIB_PATH=`dirname ${paths[0]}`
+ if [ -n "$paths" ]; then
+ paths=($paths)
+ JNI_LIB_PATH=`dirname ${paths[0]}`
+ fi
shopt -u nullglob
fi