1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import "common.h"
  27 #import "com_sun_glass_ui_mac_MacTimer.h"
  28 
  29 #import "com_sun_glass_ui_Timer.h"
  30 
  31 #import "ProcessInfo.h"
  32 #import "GlassMacros.h"
  33 #import "GlassHelper.h"
  34 #import "GlassTimer.h"
  35 
  36 #include <sys/time.h>
  37 
  38 //#define VERBOSE
  39 #ifndef VERBOSE
  40     #define LOG(MSG, ...)
  41 #else
  42     #define LOG(MSG, ...) GLASS_LOG(MSG, ## __VA_ARGS__);
  43 #endif
  44 
  45 #define HIGH_PRIORITY_TIMER         1
  46 
  47 static void *_GlassTimerTask(void *data)
  48 {
  49     pthread_setname_np("Glass Timer Thread");
  50 
  51     GlassTimer *timer = (GlassTimer*)data;
  52     if (timer != NULL)
  53     {
  54         jint error = (*MAIN_JVM)->AttachCurrentThreadAsDaemon(MAIN_JVM, (void **)&timer->_env, NULL);
  55         if (error == 0)
  56         {
  57             timer->_running = YES;
  58             while (timer->_running == YES)
  59             {
  60                 int64_t now = getTimeMicroseconds();
  61                 {
  62                     if (timer->_runnable != NULL)
  63                     {
  64                         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  65                         {
  66                             (*timer->_env)->CallVoidMethod(timer->_env, timer->_runnable, jRunnableRun);
  67                         }
  68                         [pool drain];
  69                         GLASS_CHECK_EXCEPTION(timer->_env);
  70                     }
  71                 }
  72                 int64_t duration = (getTimeMicroseconds() - now);
  73                 int64_t sleep = (timer->_period - duration);
  74                 if ((sleep < 0) || (sleep > timer->_period))
  75                 {
  76 #if 1
  77                     sched_yield();
  78 #else
  79                     pthread_yield_np();
  80 #endif
  81                 }
  82                 else
  83                 {
  84                     usleep((useconds_t)sleep);
  85                 }
  86             }
  87 
  88             if (timer->_runnable != NULL)
  89             {
  90                 (*timer->_env)->DeleteGlobalRef(timer->_env, timer->_runnable);
  91             }
  92             timer->_runnable = NULL;
  93 
  94             NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  95             {
  96                 [timer release];
  97             }
  98             [pool drain];
  99 
 100             (*MAIN_JVM)->DetachCurrentThread(MAIN_JVM);
 101         }
 102         else
 103         {
 104             NSLog(@"ERROR: Glass could not attach Timer _thread to VM, result:%d\n", (int)error);
 105         }
 106     }
 107 
 108     return NULL;
 109 }
 110 
 111 CVReturn CVOutputCallback(CVDisplayLinkRef displayLink,
 112                           const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime,
 113                           CVOptionFlags flagsIn, CVOptionFlags *flagsOut,
 114                           void *displayLinkContext) {
 115 
 116     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 117 
 118     if (displayLinkContext != NULL)
 119     {
 120         GlassTimer *timer = (GlassTimer*)displayLinkContext;
 121 
 122         // Attach the thread every time.  Cocoa changes the thread when a new
 123         // display resolution is selected so we need to make sure that we are
 124         // able to call into Java.  Attaching the same thread multiple times
 125         // does nothing.
 126         jint error = (*MAIN_JVM)->AttachCurrentThreadAsDaemon(MAIN_JVM, (void **)&timer->_env, NULL);
 127         if (error == 0)
 128         {
 129             if (timer->_runnable != NULL)
 130             {
 131                 (*timer->_env)->CallVoidMethod(timer->_env, timer->_runnable, jRunnableRun);
 132             }
 133 
 134               // Do not detach the thread - continuously attaching and detaching a thread
 135               // kills some debuggers making it impossible to step through Prism.  Since
 136               // the thread is attached as a daemon, it will not stop Java from exiting.
 137 //            error = (*MAIN_JVM)->DetachCurrentThread(MAIN_JVM);
 138 //            if (error != JNI_OK) {
 139 //                NSLog(@"ERROR: Glass could not detach CVDisplayLink _thread to VM, result:%d\n", (int)error);
 140 //            }
 141         } else {
 142             NSLog(@"ERROR: Glass could not attach CVDisplayLink _thread to VM, result:%d\n", (int)error);
 143         }
 144     }
 145 
 146     [pool release];
 147     return kCVReturnSuccess;
 148 }
 149 
 150 
 151 @implementation GlassTimer
 152 
 153 - (id)initWithRunnable:(jobject)runnable withEnv:(JNIEnv*)env
 154 {
 155     self = [super init];
 156     if (self != nil)
 157     {
 158         CVReturn err = CVDisplayLinkSetOutputCallback(GlassDisplayLink, &CVOutputCallback, self);
 159         if (err != kCVReturnSuccess)
 160         {
 161             NSLog(@"CVDisplayLinkSetOutputCallback error: %d", err);
 162         }
 163         else
 164         {
 165             self->_runnable = (*env)->NewGlobalRef(env, runnable);
 166         }
 167     }
 168     return self;
 169 }
 170 
 171 - (id)initWithRunnable:(jobject)runnable withEnv:(JNIEnv*)env withPeriod:(jint)period
 172 {
 173     self = [super init];
 174     if (self != nil)
 175     {
 176         self->_thread = NULL;
 177         self->_running = NO;
 178         self->_runnable = NULL;
 179         self->_period = (1000 * period); // ms --> us
 180 
 181         pthread_attr_t attr;
 182         pthread_attr_init(&attr);
 183         pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
 184         pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
 185 
 186 #if HIGH_PRIORITY_TIMER
 187         struct sched_param param;
 188         memset(&param, 0x00, sizeof(param));
 189         param.sched_priority = sched_get_priority_max(SCHED_FIFO)-1; // notice: 2nd highest priority (gznote: do we really want that?)
 190         pthread_attr_setschedparam(&attr, &param);
 191 #endif
 192 
 193         int err = pthread_create(&self->_thread, &attr, _GlassTimerTask, self);
 194         if (err == 0)
 195         {
 196             // detach thread, so its resources can be reclaimed as soon as it's finished
 197             pthread_detach(self->_thread);
 198 
 199             self->_runnable = (*env)->NewGlobalRef(env, runnable);
 200         }
 201         else
 202         {
 203             NSLog(@"GlassTimer error: pthread_create returned %d", err);
 204 
 205             self = nil;
 206         }
 207     }
 208     return self;
 209 }
 210 
 211 @end
 212 
 213 /*
 214  * Class:     com_sun_glass_ui_mac_MacTimer
 215  * Method:    _start
 216  * Signature: (Ljava/lang/Runnable;I)J
 217  */
 218 JNIEXPORT jlong JNICALL
 219 Java_com_sun_glass_ui_mac_MacTimer__1start__Ljava_lang_Runnable_2I(JNIEnv *env, jobject jThis,
 220                                                                    jobject jRunnable, jint jPeriod)
 221 {
 222     LOG("Java_com_sun_glass_ui_mac_MacTimer__1start__Ljava_lang_Runnable_2I");
 223 
 224     jlong jTimerPtr = 0L;
 225 
 226     GLASS_ASSERT_MAIN_JAVA_THREAD(env);
 227     GLASS_POOL_ENTER;
 228     {
 229         jTimerPtr = ptr_to_jlong([[GlassTimer alloc] initWithRunnable:jRunnable withEnv:env withPeriod:jPeriod]);
 230     }
 231     GLASS_POOL_EXIT;
 232     GLASS_CHECK_EXCEPTION(env);
 233 
 234     return jTimerPtr;
 235 }
 236 
 237 /*
 238  * Class:     com_sun_glass_ui_mac_MacTimer
 239  * Method:    _start
 240  * Signature: (Ljava/lang/Runnable;)J
 241  */
 242 JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_mac_MacTimer__1start__Ljava_lang_Runnable_2
 243 (JNIEnv *env, jobject jThis, jobject jRunnable)
 244 {
 245     LOG("Java_com_sun_glass_ui_mac_MacTimer__1start__Ljava_lang_Runnable_2");
 246 
 247     jlong jTimerPtr = 0L;
 248 
 249     GLASS_ASSERT_MAIN_JAVA_THREAD(env);
 250     GLASS_POOL_ENTER;
 251     {
 252         jTimerPtr = ptr_to_jlong([[GlassTimer alloc] initWithRunnable:jRunnable withEnv:env]);
 253     }
 254     GLASS_POOL_EXIT;
 255     GLASS_CHECK_EXCEPTION(env);
 256 
 257     return jTimerPtr;
 258 }
 259 
 260 /*
 261  * Class:     com_sun_glass_ui_mac_MacTimer
 262  * Method:    _stop
 263  * Signature: (J)V
 264  */
 265 JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacTimer__1stop
 266 (JNIEnv * env, jobject jRunnable, jlong jTimerPtr)
 267 {
 268     LOG("Java_com_sun_glass_ui_mac_MacTimer__1stop");
 269 
 270     GLASS_ASSERT_MAIN_JAVA_THREAD(env);
 271     GLASS_POOL_ENTER;
 272     {
 273         GlassTimer *timer = (GlassTimer*)jlong_to_ptr(jTimerPtr);
 274         if (timer->_period == 0)
 275         {
 276             // stop the display link
 277             if (GlassDisplayLink != NULL && CVDisplayLinkIsRunning(GlassDisplayLink)) {
 278                 CVDisplayLinkStop(GlassDisplayLink);
 279                 CVDisplayLinkRelease(GlassDisplayLink);
 280             }
 281         }
 282         else
 283         {
 284             timer->_running = NO;
 285         }
 286     }
 287     GLASS_POOL_EXIT;
 288     GLASS_CHECK_EXCEPTION(env);
 289 }
 290 
 291 /*
 292  * Class:     com_sun_glass_ui_mac_MacTimer
 293  * Method:    _getMinPeriod
 294  * Signature: ()I
 295  */
 296 JNIEXPORT jint JNICALL Java_com_sun_glass_ui_mac_MacTimer__1getMinPeriod
 297 (JNIEnv * env, jclass cls)
 298 {
 299     LOG("Java_com_sun_glass_ui_mac_MacTimer__1getMinPeriod");
 300 
 301     int period = 0;
 302 
 303     GLASS_ASSERT_MAIN_JAVA_THREAD(env);
 304     GLASS_POOL_ENTER;
 305     {
 306         period = 0; // in ms
 307     }
 308     GLASS_POOL_EXIT;
 309     GLASS_CHECK_EXCEPTION(env);
 310 
 311     return period;
 312 }
 313 
 314 /*
 315  * Class:     com_sun_glass_ui_mac_MacTimer
 316  * Method:    _getMaxPeriod
 317  * Signature: ()I
 318  */
 319 JNIEXPORT jint JNICALL Java_com_sun_glass_ui_mac_MacTimer__1getMaxPeriod
 320 (JNIEnv * env, jclass cls)
 321 {
 322     LOG("Java_com_sun_glass_ui_mac_MacTimer__1getMaxPeriod");
 323 
 324     int period = 0;
 325 
 326     GLASS_ASSERT_MAIN_JAVA_THREAD(env);
 327     GLASS_POOL_ENTER;
 328     {
 329         period = 1000000; // in ms (100 sec)
 330     }
 331     GLASS_POOL_EXIT;
 332     GLASS_CHECK_EXCEPTION(env);
 333 
 334     return period;
 335 }
 336 
 337 /*
 338  * Class:     com_sun_glass_ui_mac_MacTimer
 339  * Method:    _initIDs
 340  * Signature: ()V
 341  */
 342 JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacTimer__1initIDs
 343 (JNIEnv *env, jclass jClass)
 344 {
 345 
 346     initJavaIDsList(env);
 347 
 348     if (GlassDisplayLink == NULL)
 349     {
 350         CVReturn err = CVDisplayLinkCreateWithActiveCGDisplays(&GlassDisplayLink);
 351         if (err != kCVReturnSuccess)
 352         {
 353             NSLog(@"CVDisplayLinkCreateWithActiveCGDisplays error: %d", err);
 354         }
 355         err = CVDisplayLinkSetCurrentCGDisplay(GlassDisplayLink, kCGDirectMainDisplay);
 356         if (err != kCVReturnSuccess)
 357         {
 358             NSLog(@"CVDisplayLinkSetCurrentCGDisplay error: %d", err);
 359         }
 360         /*
 361          * set a null callback and start the link to prep for GlassTimer initialization
 362          */
 363         err = CVDisplayLinkSetOutputCallback(GlassDisplayLink, &CVOutputCallback, NULL);
 364         if (err != kCVReturnSuccess)
 365         {
 366             NSLog(@"CVDisplayLinkSetOutputCallback error: %d", err);
 367         }
 368         err = CVDisplayLinkStart(GlassDisplayLink);
 369         if (err != kCVReturnSuccess)
 370         {
 371             NSLog(@"CVDisplayLinkStart error: %d", err);
 372         }
 373     }
 374 }
 375