Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build AppleWin & LinApple from the same source? #274

Closed
gungwald opened this issue Apr 10, 2015 · 27 comments
Closed

Build AppleWin & LinApple from the same source? #274

gungwald opened this issue Apr 10, 2015 · 27 comments

Comments

@gungwald
Copy link

On Linux we have LinApple, but it is based on a very old version of AppleWin. So, all the improvements that have been made in AppleWin are not available in LinApple. It would be nice if it would be possible to support a Linux build directly from the AppleWin sources, if there were a way to do this without making a mess of the source code. I'm currently looking at LinApple to see how it was done. I hope to come up with some ideas on how it would be possible to build a Linux version directly from AppleWin, so that improvements could be shared across the platforms.

AppleWin runs nicely in Wine on Linux, except that it continually takes up 100% of the CPU. I'm sure it's a problem with Wine and not AppleWin. But a native Linux version would be preferable.

Thanks. Let me know what your thoughts on this are.

@tomcw
Copy link
Contributor

tomcw commented Apr 16, 2015

Please continue to post any updates or progress here.

I have no comments at the moment, as my focus is purely on Windows. But others are interested in Linux and Mac OS X (native) ports.

NB. Related to #184 and #223.

@gungwald
Copy link
Author

Great. Thanks! I have fixed a problem in LinApple with the Alt key failing to emulate the Open Apple key. But, what I changed actually seems to work in AppleWin (unchanged). I'm wondering if anyone can explain how this works:

Joystick.cpp: 279: keydown[JK_OPENAPPLE+(extended != 0)] = down;

The '+(extended != 0)' part causes the failure in LinApple. When this is removed the Alt key works fine in LinApple. What is the 'extended' variable supposed to represent, and how does this not break AppleWin as the wrong element of the array is set when extended==1?

@gungwald
Copy link
Author

My project is here: https://github.com/gungwald/applelin

I also have applied a patch posted on SourceForge that fixes LinApple under Linux on the PowerPC (Big Endian) architecture. I'm developing on Debian PowerPC 7.8 with a PowerBook G4 which I bought on e-bay for $50 just for fun.

@Michaelangel007
Copy link
Contributor

Yeah, we definitely to coordinate and get AppleWin in better cross-platform / porting shape. This is still a long term "wish list" to have official support so all efforts are appreciated even if it seems there is no traction.

@audetto
Copy link
Contributor

audetto commented May 30, 2015

I can confirm that 1.25.0.3 maxes out 1 CPU when running in wine.
Just at the ] prompt without doing anything.

What (and where in the code) does AppleWin do in its idle loop. Maybe is uses Windows calls that do not work well in wine.

@tomcw
Copy link
Contributor

tomcw commented May 31, 2015

I assume that AppleWin under Wine runs at the correct speed?

ContinueExecution() will execute 1000x 6502 cycles worth of Apple II code (ie. 1ms of Apple II time) then will call SysClk_WaitTimer() to wait for the remaining 1ms (in real wall-clock time) to expire.

SysClk_WaitTimer() calls WaitForSingleObject(), waiting on a semaphore which gets signalled by a periodic clock provided by DirectSound.

Maybe Wine's implementation of this DirectSound clock is via some sort of busy-wait loop?

@tomcw
Copy link
Contributor

tomcw commented May 31, 2015

re. I can confirm that 1.25.0.3 maxes out 1 CPU when running in wine.
See #187.

@audetto
Copy link
Contributor

audetto commented Jun 1, 2015

Thanks for the info. I had a look and I can confirm that ~ 97% of running time is inside the WaitForSingleObject().

Tried to replace the DirectSound IReferenceClock with a direct call to CreateWaitableTimer and the CPU usage drops by 50% (in wine).
I guess IReferenceClock is a more exotic feature, not virtualised well.

Even in a "do nothing" app waiting on a timer every 1ms the same pattern is visible (DirectSound vs CreateWaitableTimer), will have to ask the wine guys about it. Still it use a lot more CPU then it should.

Now, before even mentioning a patch (there is actually no bug to fix in AppleWin), CreateWaitableTimer has a resolution of 1 ms, while the code seems to allow for micro seconds periods (even though it is 1000 microseconds = 1 millisecond always).

I could not find an other Windows normal (i.e. non DirectSound) call with a resolution < 1ms.

Were there any particular reason for the choice of DirectSound for the timer event generation?

@tomcw
Copy link
Contributor

tomcw commented Jun 1, 2015

Hi,

Were there any particular reason for the choice of DirectSound for the timer event generation?

When we switched to DirectSound for the timer, AppleWin was still supporting Win98(*). But CreateWaitableTimer has a minimum supported OS of WinXP:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682492(v=vs.85).aspx

(*) In fact, it will only be the next 1.26 release that we will drop support for Win98 by building with VS2008 (instead of VS2005). With VS2008 the minimum OS becomes Win2000. (Ref #124)

@audetto
Copy link
Contributor

audetto commented Jun 3, 2015

I will report some more details of my findings, but so far I've found 4 timer functions in Windows

  • IReferenceClock->AdvisePeriodic (from 2000)
  • CreateTimerQueueTimer (from XP / 2003)
  • timeSetEvent (from XP / 2003)
  • CreateWaitableTImer (from XP / 2003)

In wine the first one seems to be implemented as a spin lock, the others as proper wait.
The last 3 have a relatively big cost to call them (comparing wine to Windows), but perform much better than the first.

Moreover using the last 3, the CPU usage in wine decreases when "nExecutionPeriodUsec" in Applewin.cpp:168 increases (as they are called less often).

So anybody willing to recompile Applewin to run it in wine, they could change timer function and optionally increase the execution period (with AdvisePeriodic it is useless as it behaves similarly to a spinlock).

@audetto
Copy link
Contributor

audetto commented Jun 6, 2015

I've timed the 4 implementations above in a test app (http://pastebin.com/wt4iTL78)

With periods of 1 ms and out of a total wait of 10 sec, I get the following times spent in "sys" using the "time" app

method 1ms 5ms
IReferenceClock 4.05s 3.95s
CreateTimerQueueTimer 0.57s 0.28s
timeSetEvent 0.54s 0.20s
CreateWaitableTimer 0.33s 0.15s

The feeling is a spin lock for the first.

As soon as I get a VS install running somewhere (wine / VB) I will post a modified AppleWin for other Linux users interested to try.

@audetto
Copy link
Contributor

audetto commented Jun 7, 2015

Good news,

wine developers seems to have found an issue in IReferenceClock->AdvisePeriodic

see here

http://article.gmane.org/gmane.comp.emulators.wine.devel/102107

once I get (or compile) such a wine I will let you know

@tomcw
Copy link
Contributor

tomcw commented Jun 7, 2015

Very interesting. Thanks for sharing this (and pursuing this with the Wine dev team).

@gungwald
Copy link
Author

Thanks from me too. I can't wait to see it work.

 On Sunday, June 7, 2015 4:41 PM, TomCh <notifications@github.com> wrote:

Very interesting. Thanks for sharing this (and pursuing this with the Wine dev team).—
Reply to this email directly or view it on GitHub.

@audetto
Copy link
Contributor

audetto commented Jun 16, 2015

I've pushed a code change to use CreateWaitableTimer, which behaves better for the time being and according to wine-devel is anyway better implemented then all the others.

https://github.com/audetto/AppleWin branch wine, if you want to compile it.

I still have an other issue with sound, but I will open an other ticket for it.

@audetto
Copy link
Contributor

audetto commented Jun 25, 2015

The timer patch should land in wine 1.7.45

http://source.winehq.org/git/wine.git/commit/b513e07c55504f623baf8d838d00ac628eac7614

@gungwald
Copy link
Author

Very cool. Thanks!

 On Thursday, June 25, 2015 4:27 PM, Andrea <notifications@github.com> wrote:

The timer patch should land in wine 1.7.45http://source.winehq.org/git/wine.git/commit/b513e07c55504f623baf8d838d00ac628eac7614—
Reply to this email directly or view it on GitHub.

@Michaelangel007
Copy link
Contributor

That's great news -- Thanks for taking the time to investigate and patch

Sent from my iPhone

On Jun 25, 2015, at 1:27 PM, Andrea notifications@github.com wrote:

The timer patch should land in wine 1.7.45

http://source.winehq.org/git/wine.git/commit/b513e07c55504f623baf8d838d00ac628eac7614


Reply to this email directly or view it on GitHub.

@audetto
Copy link
Contributor

audetto commented Jul 3, 2015

wine 1.7.45 is out but I don't think I works as it should.
Basically with my app
http://pastebin.com/K55znWFQ

I compare the requested period with the actual period, and for 1ms wait things can get really bad.
In the office it goes to 8ms average actual wait for 1ms requested!!!!!
At home better, 1.4ms.

It used to be 1ms in both cases.

I reported the fact, let's see if they can improve the patch.

@Michaelangel007
Copy link
Contributor

Thanks for including the source for the test case!

Since pastebin is not permanent .... including it here as a reference.

@Michaelangel007
Copy link
Contributor

    #include <windows.h>
    #include <stdio.h>
    #include <uuids.h>
    #include <dsound.h>
    #include <math.h>
    #include <strmif.h>

    // compile as
    // winegcc -g timers.cpp -lwinmm -lole32 -lstrmiids

    static int totalTime = 5; // sec

     void wait(HANDLE object, int milliseconds)
     {
      LARGE_INTEGER frequency;
      QueryPerformanceFrequency(&frequency);
      double f = frequency.QuadPart;
      double period = 1000.0 / f; // in ms

      LONGLONG previous = 0L;
      double sum = 0.;
      double sum2 = 0.;

      int k = 0;
      do
      {
        WaitForSingleObject(object, INFINITE);
        ++k;

        LARGE_INTEGER t1;
        QueryPerformanceCounter(&t1);

        if (k > 1)
        {
          double thisGap = double(t1.QuadPart - previous) * period;
          sum += thisGap;
          sum2 += thisGap * thisGap;
        }
        previous = t1.QuadPart;
      }
      while (k < totalTime * 1000 / milliseconds); // number of waits for 10 seconds

      double average = double(sum) / double(k - 1);
      double average2 = double(sum2) / double(k - 1);
      double stddev = sqrt(average2 - average * average);

      printf("k: %d\n", k);
      printf("average: %g\n", average);
      printf("stddev: %g\n", stddev);
    }

    void main1(int milliseconds)
    {
      HANDLE hTimer;
      LARGE_INTEGER liDueTime = { 0 };

      printf("CreateWaitableTimer\n");

      hTimer = CreateWaitableTimer (NULL, FALSE, NULL);
      SetWaitableTimer(hTimer, &liDueTime, milliseconds, NULL, NULL, 0);

      wait(hTimer, milliseconds);
    }

    void main2(int milliseconds)
    {
      HANDLE g_hSemaphore;
      IReferenceClock *g_RefClock;
      DWORD_PTR g_dwAdviseToken;

      printf("IReferenceClock\n");

      g_hSemaphore = CreateSemaphore(NULL, 0, 1, NULL);
      CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
      CoCreateInstance(CLSID_SystemClock, NULL, CLSCTX_INPROC, IID_IReferenceClock, (LPVOID*)&g_RefClock);

      REFERENCE_TIME rtPeriod = (REFERENCE_TIME)(10000 * milliseconds);
      REFERENCE_TIME rtNow;

      g_RefClock->GetTime(&rtNow);

      // VS Build
    #ifdef _MSC_VER
      g_RefClock->AdvisePeriodic(rtNow + rtPeriod, rtPeriod, g_hSemaphore, &g_dwAdviseToken);
    #else
      g_RefClock->AdvisePeriodic(rtNow + rtPeriod, rtPeriod, (HSEMAPHORE)g_hSemaphore, &g_dwAdviseToken);
    #endif

      wait(g_hSemaphore, milliseconds);
    }

    VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired)
    {
      HANDLE & gDoneEvent = *(HANDLE*)lpParam;
      SetEvent(gDoneEvent);
    }

    void main3(int milliseconds)
    {
      HANDLE hTimer;
      HANDLE hTimerQueue;
      HANDLE gDoneEvent;

      printf("CreateTimerQueueTimer\n");

      gDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      hTimerQueue = CreateTimerQueue();
      CreateTimerQueueTimer(&hTimer, hTimerQueue, (WAITORTIMERCALLBACK)TimerRoutine, &gDoneEvent, 0, milliseconds, 0);

      wait(gDoneEvent, milliseconds);
    }

    void main4(int milliseconds)
    {
      HANDLE gDoneEvent;

      printf("timeSetEvent\n");

      gDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

      int resolution = 0;
      timeSetEvent(milliseconds, resolution, (LPTIMECALLBACK)gDoneEvent, 0, TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);

      wait(gDoneEvent, milliseconds);
    }

    int main(int argc, char * argv[])
    {
      if (argc == 3)
      {
        const char * mstr = argv[1];
        int milliseconds = atoi(mstr);

        printf("Waiting every %d milliseconds for %d seconds...\n", milliseconds, totalTime);

        char c = argv[2][0];
        switch (c)
        {
        case '1': main1(milliseconds); break;
        case '2': main2(milliseconds); break;
        case '3': main3(milliseconds); break;
        case '4': main4(milliseconds); break;
        }
      }
      return 0;
    }

@gungwald
Copy link
Author

With the latest versions of AppleWin and Wine on Fedora 21, I no longer see the processes taking up 100% of the CPU. I'm not sure if Wine is doing exactly the right wait time, but it is not causing a problem anymore. Thanks for the fixes on this.

This solves the problem for Linux on Intel-based hardware because Wine can successfully be used to run AppleWin. However, a good Apple II emulator for Linux non-Intel hardware, such as ARM and PowerPC, is still lacking. I'm not up to working on that right now so I'll close this issue since my main problem was solved. I may come back to it later and will re-open something at that time. Thanks again.

@tomcw
Copy link
Contributor

tomcw commented Aug 24, 2015

Hi,

With the latest versions of AppleWin and Wine on Fedora 21

For future reference can you just confirm which versions of AppleWin and Wine you used? Thanks.

@gungwald
Copy link
Author

AppleWin 1.25.0.3
Wine 1.7.47
Fedora 22

Note that I misstated the Fedora version before as 21. It is actually 22.

@jskDr
Copy link

jskDr commented Aug 25, 2015

I have a stupid question now. Why it is called as no AppleLin but it is
called as LinApple.
It makes me confuse to think they are not based on the same root.
Why don't we change the name to AppleLin?
To me, AppleLin seems to be more reasonable to understand what it is and
what it is based on.
If there is any special reason which I don't know yet, it is okay to use
LinApple.

I am sorry for putting totally different story from the discussion although
it is about LinApple.
I am just very pleasure to hear that I can use AppleWin (or LinApple) again
in Linux,
which now I am mostly using for my research rather than Win.

James

On Tue, Aug 25, 2015 at 5:46 PM, Bill Chatfield notifications@github.com
wrote:

AppleWin 1.25.0.3
Wine 1.7.47
Fedora 22

Note that I misstated the Fedora version before as 21. It is actually 22.


Reply to this email directly or view it on GitHub
#274 (comment).

Best regards,
(James) Sungjin Kim, Ph.D.

@gungwald
Copy link
Author

The original person who ported AppleWin to Linux just liked the sound of LinApple better, so he used that name. My fork of LinApple, I did call AppleLin. Now I'm so confused I can't ever remember what the correct names are.

@jskDr
Copy link

jskDr commented Aug 26, 2015

That's so much understandable. LinApple sounds better for me as well, like
lime apple. Of cause, AppleWin sounds better than WinApple. We want to win
by Apple but not want to win Apple.
I am so sorry about saying just fun talk here. But how to call is important
to success a task as usual. I spend a lot of time to define the name of a
project when I start new one.

James S Kim
Harvard
sungjinkim@fas.harvard.edu

On Wed, Aug 26, 2015 at 12:25 AM, Bill Chatfield notifications@github.com
wrote:

The original person who ported AppleWin to Linux just liked the sound of
LinApple better, so he used that name. My fork of LinApple, I did call
AppleLin. Now I'm so confused I can't ever remember what the correct names
are.


Reply to this email directly or view it on GitHub
#274 (comment).

Best regards,
(James) Sungjin Kim, Ph.D.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants