Epsilon has only access to the "workshop" RAM archive, it would be nice to add access to Casio filesystem. That's in my opinion low priority vs having persistence, but perhaps both aspects are linked (I mean when you implement one feature, the other is easy to implement).

3d and all numeric code inside KhiCAS is using the double type. The 2d grapher inside KhiCAS is faster than Upsilon, probably because Poincare Epsilon code evaluation is slower than KhiCAS evaluation. You can not change that easily. Epsilon is C++-ish, while KhiCAS is C-ish which is most of the time faster because nothing is hidden, and moreover I pay special attention to optimizations when coding.
[OT] The KhiCAS UI has indeed a learning curve in order to run it efficiently. Probably because programming UI is not my domain (if someone want to improve it he is welcome!), but probably also because running a CAS requires more prerequisites (both in computer science and in maths) than running a purely numeric software. Most of the things you will learn using KhiCAS will be useful with any CAS. [/OT]
I have a built with very preliminary persistance: you can save or restore the scriptstore with shift-OFF
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/epsilon.g3a
@Heath: I'm sure you can do much better than I did!

Modified file ion/src/simulator/fxcg/main.cpp

Code:

#include "main.h"
#include "display.h"
#include "platform.h"

#include <gint/display-cg.h>
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/keyboard.h>

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

#include <ion.h>
#include <ion/events.h>

void * storage_address(); // ion/src/simulator/shared/platform_info.cpp
const int storage_length=32768; // it's 60000 in Upsilon bu 32768 in Epsilon
const char * storage_name="nwstore.nws";

int load_state(const char * fname){
  FILE * f=fopen(fname,"rb");
  if (f){
    fread(storage_address(),1,storage_length,f);
    fclose(f);
    return 1;
  }
  return 0;


int save_state(const char * fname){
  if (1 || Ion::Storage::sharedStorage()->numberOfRecords()){
    FILE * f=fopen(storage_name,"wb");
    if (f){
      fwrite(storage_address(),1,storage_length,f);
      fclose(f);
      return 1;
    }
    return 0;
  }
  return 2;
}

extern "C" {
int main() {
  Ion::Simulator::Main::init();
  ion_main(0, NULL);
  Ion::Simulator::Main::quit();

  return 0;
}
}

namespace Ion {
namespace Simulator {
namespace Main {

static bool sNeedsRefresh = false;

void init() {
  Ion::Simulator::Display::init();
  setNeedsRefresh();
}

void setNeedsRefresh() {
  sNeedsRefresh = true;
}

void refresh() {
  if (!sNeedsRefresh) {
    return;
  }

  Display::draw();

  sNeedsRefresh = false;
}

void quit() {
  Ion::Simulator::Display::quit();
}

void EnableStatusArea(int opt) {
  __asm__ __volatile__ (
    ".align 2 \n\t"
    "mov.l 2f, r2 \n\t"
    "mov.l 1f, r0 \n\t"
    "jmp @r2 \n\t"
    "nop \n\t"
    ".align 2 \n\t"
    "1: \n\t"
    ".long 0x02B7 \n\t"
    ".align 4 \n\t"
    "2: \n\t"
    ".long 0x80020070 \n\t"
  );
}

extern "C" void *__GetVRAMAddress(void);

uint8_t xyram_backup[16 * 1024];
uint8_t ilram_backup[4 * 1024];

void worldSwitchHandler(void (*worldSwitchFunction)(), bool prepareVRAM) {
  // Back up XYRAM
  uint8_t* xyram = (uint8_t*) 0xe500e000;
  memcpy(xyram_backup, xyram, 16 * 1024);
  // Back up ILRAM
  uint8_t* ilram = (uint8_t*) 0xe5200000;
  memcpy(ilram_backup, ilram, 4 * 1024);

  if (prepareVRAM) {
    // Copying the screen to the OS's VRAM avoids a flicker when powering on
    uint16_t* dst = (uint16_t *) __GetVRAMAddress();
    uint16_t* src = gint_vram + 6;

    for (int y = 0; y < 216; y++, dst += 384, src += 396) {
      for (int x = 0; x < 384; x++) {
        dst[x] = src[x];
      }
    }

    // Disable the status area
    EnableStatusArea(3);
  }

  worldSwitchFunction();

  // Restore XYRAM
  memcpy(xyram, xyram_backup, 16 * 1024);
  // Restore ILRAM
  memcpy(ilram, ilram_backup, 4 * 1024);
}

void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) {

  dclear(C_WHITE);
  dtext(1,1, C_BLACK, "STO: save state");
  dtext(1,17,C_BLACK,"EXE: load state");
  dtext(1,33,C_BLACK,"other key: OFF/leave");
  dupdate();
#if 0
  int opt=GETKEY_DEFAULT,timeout=3; key_event_t ev=getkey_opt(opt,&timeout);
#else
  key_event_t ev=getkey();
#endif
  int key=ev.key;
  if (key==KEY_STORE){
    gint_world_switch(GINT_CALL(save_state,storage_name));
    setNeedsRefresh();
    return;
  }
  else if (key==KEY_EXE){
    gint_world_switch(GINT_CALL(load_state,storage_name));
    setNeedsRefresh();
    return;
  }

  gint_world_switch(GINT_CALL(worldSwitchHandler, powerOffSafeFunction, prepareVRAM));
}

}
}
}
I have updated https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/epsilon.g3a. I have now working code to save/restore the scriptstore at startup/exit. This is done in the power off function in main.cpp and with a static bool in the home app code. I have removed the code to save/load with OFF. Additionnaly you can now save/load up to 9 calculator states from the home application with the sto key (the key above AC/ON) and the VARS key.
This is still experimental and I get crashes from time to time. If the addin crashes, delete the file nwstore.nws (auto-save) and nwstate1-9.nws.
New code for apps/home/controller.cpp

Code:

#include "controller.h"
#include "app.h"
#include <apps/home/apps_layout.h>
#include "../apps_container.h"
#include "../global_preferences.h"
#include "../exam_mode_configuration.h"

extern "C" {
#include <assert.h>
}

#ifdef _FXCG
#include "../../ion/src/simulator/fxcg/platform.h"

#include <gint/bfile.h>
#include <gint/display-cg.h>
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/keyboard.h>

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

#include <ion.h>
#include <ion/events.h>

void * storage_address(); // ion/src/simulator/shared/platform_info.cpp
static const int storage_length=8192; // 60000 in Upsilon, 32768 in Epsilon
// k_storageSize = 60000; in ion/include/ion/internal_storage.h
static const char * storage_name="nwstore.nws";

static int load_state(const char * fname){
  FILE * f=fopen(fname,"rb");
  if (f){
    fread(storage_address(),1,storage_length,f);
    fclose(f);
    return 1;
  }
  return 0;


static void convert(const char * fname,unsigned short * pFile){
  for ( ;*fname;++fname,++pFile)
    *pFile=*fname;
  *pFile=0;
}

static int save_state(const char * fname){
  if (Ion::Storage::sharedStorage()->numberOfRecords()){
#if 0
    unsigned short pFile[512];
    convert(fname,pFile);
    int hf = BFile_Open(pFile, BFile_WriteOnly); // Get handle
    // cout << hf << endl << "f:" << filename << endl; Console_Disp();
    if (hf<0){
      int l=storage_length;
      BFile_Create(pFile,0,&l);
      hf = BFile_Open(pFile, BFile_WriteOnly);
    }
    if (hf < 0)
      return 0;
    int l=BFile_Write(hf,storage_address(),storage_length);
    BFile_Close(hf);
    if (l==storage_length)
      return 1;
    return -1;
#else
    const char * ptr=(const char *)storage_address();
    // find store size
    int S=4;
    for (ptr+=4;;){
      size_t L=ptr[1]*256+ptr[0];
      ptr+=L;
      S+=L;
      if (L==0) break;
    }
    S = ((S+1023)/1024)*1024;
    FILE * f=fopen(fname,"wb");
    if (f){
      fwrite(storage_address(),1,S,f);
      fclose(f);
      return S;
    }
    return 0;
#endif
  }
  return 2;
}
#endif

#ifdef HOME_DISPLAY_EXTERNALS
#include "../external/external_icon.h"
#include "../external/archive.h"
#include <string.h>
#endif

namespace Home {

Controller::ContentView::ContentView(Controller * controller, SelectableTableViewDataSource * selectionDataSource) :
  m_selectableTableView(controller, controller, &m_backgroundView, selectionDataSource, controller),
  m_backgroundView()
{
  m_selectableTableView.setVerticalCellOverlap(0);
  m_selectableTableView.setMargins(0, k_sideMargin, k_bottomMargin, k_sideMargin);
  static_cast<ScrollView::BarDecorator *>(m_selectableTableView.decorator())->verticalBar()->setMargin(k_indicatorMargin);
}

SelectableTableView * Controller::ContentView::selectableTableView() {
  return &m_selectableTableView;
}

void Controller::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
  m_selectableTableView.drawRect(ctx, rect);
}

void Controller::ContentView::reloadBottomRow(SimpleTableViewDataSource * dataSource, int numberOfIcons, int numberOfColumns) {
  if (numberOfIcons % numberOfColumns) {
    /* We mark the missing icons on the last row as dirty. */
    for (int i = 0; i < numberOfColumns; i++) {
      if (i >= numberOfIcons % numberOfColumns) {
        markRectAsDirty(KDRect(dataSource->cellWidth()*i, dataSource->cellHeight(), dataSource->cellWidth(), dataSource->cellHeight()));
      }
    }
  }
}

BackgroundView * Controller::ContentView::backgroundView() {
  return &m_backgroundView;
}

int Controller::ContentView::numberOfSubviews() const {
  return 1;
}

View * Controller::ContentView::subviewAtIndex(int index) {
  assert(index == 0);
  return &m_selectableTableView;
}

void Controller::ContentView::layoutSubviews(bool force) {
  m_selectableTableView.setFrame(bounds(), force);
  m_backgroundView.setFrame(KDRect(0, Metric::TitleBarHeight, Ion::Display::Width, Ion::Display::Height-Metric::TitleBarHeight), force);
  m_backgroundView.updateDataValidity();
}

Controller::Controller(Responder * parentResponder, SelectableTableViewDataSource * selectionDataSource, ::App * app) :
  ViewController(parentResponder),
  m_view(this, selectionDataSource)
{
  m_app = app;
  for (int i = 0; i < k_maxNumberOfCells; i++) {
    m_cells[i].setBackgroundView(m_view.backgroundView());
  }

  m_view.backgroundView()->setDefaultColor(Palette::HomeBackground);


#ifdef HOME_DISPLAY_EXTERNALS
    int index = External::Archive::indexFromName("wallpaper.obm");
    if(index > -1) {
      External::Archive::File image;
      External::Archive::fileAtIndex(index, image);
      m_view.backgroundView()->setBackgroundImage(image.data);
    }
#endif
}

static constexpr Ion::Events::Event home_fast_navigation_events[] = {
    Ion::Events::Seven, Ion::Events::Eight, Ion::Events::Nine,
    Ion::Events::Four, Ion::Events::Five, Ion::Events::Six,
    Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
    Ion::Events::Zero, Ion::Events::Dot, Ion::Events::EE
};

bool Controller::handleEvent(Ion::Events::Event event) {
#ifdef _FXCG
  static bool firstrun=true;
  if (firstrun){
    gint_world_switch(GINT_CALL(load_state,storage_name));
    firstrun=false;
  }   
  if (Ion::Storage::sharedStorage()->numberOfRecords() && event == Ion::Events::Sto){
    dclear(C_WHITE);
    dtext(1,1, C_BLACK, "Key 1 to 9: save state");
    dtext(1,17,C_BLACK,"to file nwstate1 to 9");
    dtext(1,33,C_BLACK,"Press any other key to cancel");
    dupdate();
#if 0
    int opt=GETKEY_DEFAULT,timeout=3; key_event_t ev=getkey_opt(opt,&timeout);
#else
    key_event_t ev=getkey();
#endif
    int key=ev.key;
    char buf[]="nwstate0.nws";
    if (key>=KEY_1 && key<=KEY_9){
      buf[7]='1'+(key-KEY_1);
      dclear(C_WHITE);
      dtext(1,1, C_BLACK, "Saving");
      dtext(1,17,C_BLACK,buf);
      dupdate();
      int l=gint_world_switch(GINT_CALL(save_state,buf));
      char buf2[]="00000";
      for (int pos=0;l;l/=10,++pos){
   buf2[sizeof(buf2)-1-pos] += l % 10;
      }
      dtext(1,33,C_BLACK,"Length");
      dtext(1,49,C_BLACK,buf2);
      dtext(1,65,C_BLACK,"Press any key");
      dupdate();
      getkey();
      ((App*)m_app)->redraw();
    }
  }
  if (event == Ion::Events::Var) {
    dclear(C_WHITE);
    dtext(1,1, C_BLACK, "Key 1 to 9: load state");
    dtext(1,17,C_BLACK,"from file nwstate1 to 9");
    dtext(1,33,C_BLACK,"Current context will be lost!");
    dtext(1,49,C_BLACK,"Press any other key to cancel");
    dupdate();
#if 0
    int opt=GETKEY_DEFAULT,timeout=3; key_event_t ev=getkey_opt(opt,&timeout);
#else
    key_event_t ev=getkey();
#endif
    int key=ev.key;
    char buf[]="nwstate0.nws";
    if (key>=KEY_1 && key<=KEY_9){
      buf[7]='1'+(key-KEY_1);
      dclear(C_WHITE);
      dtext(1,1, C_BLACK, "Loading");
      dtext(1,17,C_BLACK,buf);
      dupdate();
      int l=gint_world_switch(GINT_CALL(load_state,buf));
      char buf2[]="0";
      buf2[0] += l;
      if (l==0)
   dtext(1,33,C_BLACK,"Error reading state");
      if (l==1)
   dtext(1,33,C_BLACK,"Success reading state");
      dtext(1,49,C_BLACK,buf2);
      dtext(1,65,C_BLACK,"Press any key");
      dupdate();
      getkey();
      ((App*)m_app)->redraw();
    }
  }
#endif // _FXCG
  if (event == Ion::Events::OK || event == Ion::Events::EXE) {
    AppsContainer * container = AppsContainer::sharedAppsContainer();

    int index = selectionDataSource()->selectedRow()*k_numberOfColumns+selectionDataSource()->selectedColumn()+1;
#ifdef HOME_DISPLAY_EXTERNALS
    if (index >= container->numberOfApps()) {
      if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSymNoText || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSym) {
        App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2);
      } else {
        External::Archive::File executable;
        if (External::Archive::executableAtIndex(index - container->numberOfApps(), executable)) {
          uint32_t res = External::Archive::executeFile(executable.name, ((App *)m_app)->heap(), ((App *)m_app)->heapSize());
          ((App*)m_app)->redraw();
          switch(res) {
            case 0:
              break;
            case 1:
              Container::activeApp()->displayWarning(I18n::Message::ExternalAppApiMismatch);
              break;
            case 2:
              Container::activeApp()->displayWarning(I18n::Message::StorageMemoryFull1);
              break;
            default:
              Container::activeApp()->displayWarning(I18n::Message::ExternalAppExecError);
              break;
          }
          return true;
        }
      }
    } else {
#endif
    ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index);
    if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->examinationLevel(), GlobalPreferences::sharedGlobalPreferences()->examMode())) {
      App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2);
    } else {
      bool switched = container->switchTo(selectedSnapshot);
      assert(switched);
      (void) switched; // Silence compilation warning about unused variable.
    }
#ifdef HOME_DISPLAY_EXTERNALS
    }
#endif
    return true;
  }

  if (event == Ion::Events::Home || event == Ion::Events::Back) {
    return m_view.selectableTableView()->selectCellAtLocation(0, 0);
  }

  if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows() - 1) {
    return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow() + 1);
  }
  if (event == Ion::Events::Left && selectionDataSource()->selectedRow() > 0) {
    return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns() - 1, selectionDataSource()->selectedRow() - 1);
  }

  // Handle fast home navigation
  for(int i = 0; i < std::min((int) (sizeof(home_fast_navigation_events) / sizeof(Ion::Events::Event)), this->numberOfIcons()); i++) {
    if (event == home_fast_navigation_events[i]) {
      int row = i / k_numberOfColumns;
      int column = i % k_numberOfColumns;
      // Get if app is already selected
      if (selectionDataSource()->selectedRow() == row && selectionDataSource()->selectedColumn() == column) {
        // If app is already selected, launch it
        return handleEvent(Ion::Events::OK);
      }
      // Else, select the app
      return m_view.selectableTableView()->selectCellAtLocation(column, row);
    }
  }

  return false;
}

void Controller::didBecomeFirstResponder() {
  if (selectionDataSource()->selectedRow() == -1) {
    selectionDataSource()->selectCellAtLocation(0, 0);
  }
  Container::activeApp()->setFirstResponder(m_view.selectableTableView());
}

void Controller::viewWillAppear() {
  KDIonContext::sharedContext()->zoomInhibit = true;
  KDIonContext::sharedContext()->updatePostProcessingEffects();
}

void Controller::viewDidDisappear() {
  KDIonContext::sharedContext()->zoomInhibit = false;
  KDIonContext::sharedContext()->updatePostProcessingEffects();
}

View * Controller::view() {
  return &m_view;
}

int Controller::numberOfRows() const {
  return ((numberOfIcons() - 1) / k_numberOfColumns) + 1;
}

int Controller::numberOfColumns() const {
  return k_numberOfColumns;
}

KDCoordinate Controller::cellHeight() {
  return k_cellHeight;
}

KDCoordinate Controller::cellWidth() {
  return k_cellWidth;
}

HighlightCell * Controller::reusableCell(int index) {
  return &m_cells[index];
}

int Controller::reusableCellCount() const {
  return k_maxNumberOfCells;
}

void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
  AppCell * appCell = (AppCell *)cell;
  AppsContainer * container = AppsContainer::sharedAppsContainer();
  int appIndex = (j * k_numberOfColumns + i) + 1;
  if (appIndex >= container->numberOfApps()) {
#ifdef HOME_DISPLAY_EXTERNALS
    External::Archive::File app_file;


    if (External::Archive::executableAtIndex(appIndex - container->numberOfApps(), app_file)) {
      char temp_name_buffer[100];
      strlcpy(temp_name_buffer, app_file.name, 94);
      strlcat(temp_name_buffer, ".icon", 99);

      int img_index = External::Archive::indexFromName(temp_name_buffer);

      if (img_index != -1) {
        External::Archive::File image_file;
        if (External::Archive::fileAtIndex(img_index, image_file)) {
          // const Image* img = new Image(55, 56, image_file.data, image_file.dataLength);
          appCell->setExtAppDescriptor(app_file.name, image_file.data, image_file.dataLength);
        } else {
          appCell->setExtAppDescriptor(app_file.name, ImageStore::ExternalIcon);
        }
      } else {
        appCell->setExtAppDescriptor(app_file.name, ImageStore::ExternalIcon);
      }

      appCell->setVisible(true);
    } else {
      appCell->setVisible(false);
    }
#else
    appCell->setVisible(false);
#endif
  } else {
    appCell->setVisible(true);
    ::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor();
    appCell->setAppDescriptor(descriptor);
  }
}

int Controller::numberOfIcons() const {
  AppsContainer * container = AppsContainer::sharedAppsContainer();
  assert(container->numberOfApps() > 0);
#ifdef HOME_DISPLAY_EXTERNALS
  return container->numberOfApps() - 1 + External::Archive::numberOfExecutables();
#else
  return container->numberOfApps() - 1;
#endif
}

void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) {
  if (withinTemporarySelection) {
    return;
  }
  /* To prevent the selectable table view to select cells that are unvisible,
   * we reselect the previous selected cell as soon as the selected cell is
   * unvisible. This trick does not create an endless loop as we ensure not to
   * stay on a unvisible cell and to initialize the first cell on a visible one
   * (so the previous one is always visible). */
  int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1;
  if (appIndex >= this->numberOfIcons()+1) {
    t->selectCellAtLocation((this->numberOfIcons()%k_numberOfColumns)-1, (this->numberOfIcons() / k_numberOfColumns));
  }
}

void Controller::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) {
  /* If the number of apps (including home) is != 3*n+1, when we display the
   * lowest icons, the other(s) are empty. As no icon is thus redrawn on the
   * previous ones, the cell is not cleaned. We need to redraw a white rect on
   * the cells to hide the leftover icons. Ideally, we would have redrawn all
   * the background in white and then redraw visible cells. However, the
   * redrawing takes time and is visible at scrolling. Here, we avoid the
   * background complete redrawing but the code is a bit
   * clumsy. */
  if (t->selectedRow() == numberOfRows()-1) {
    m_view.reloadBottomRow(this, this->numberOfIcons(), k_numberOfColumns);
  }
}

SelectableTableViewDataSource * Controller::selectionDataSource() const {
  return App::app()->snapshot();
}

}


New code for ion/src/simulator/fxcg/main.cpp

Code:

#include "main.h"
#include "display.h"
#include "platform.h"

#include <gint/display-cg.h>
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/keyboard.h>

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

#include <ion.h>
#include <ion/events.h>

void * storage_address(); // ion/src/simulator/shared/platform_info.cpp
static const int storage_length=8192; // 60000 in Upsilon, 32768 in Epsilon
// k_storageSize = 60000; in ion/include/ion/internal_storage.h
static const char * storage_name="nwstore.nws";

static int load_state(const char * fname){
  FILE * f=fopen(fname,"rb");
  if (f){
    fread(storage_address(),1,storage_length,f);
    fclose(f);
    return 1;
  }
  return 0;


static int save_state(const char * fname){
  if (1 || Ion::Storage::sharedStorage()->numberOfRecords()){
    const char * ptr=(const char *)storage_address();
    // find store size
    int S=4;
    for (ptr+=4;;){
      size_t L=ptr[1]*256+ptr[0];
      ptr+=L;
      S+=L;
      if (L==0) break;
    }
    S = ((S+1023)/1024)*1024;
    FILE * f=fopen(fname,"wb");
    if (f){
      fwrite(storage_address(),1,S,f);
      fclose(f);
      return S;
    }
    return 0;
  }
  return 2;
}

extern "C" {
int main() {
  Ion::Simulator::Main::init();
  ion_main(0, NULL);
  Ion::Simulator::Main::quit();

  return 0;
}
}

namespace Ion {
namespace Simulator {
namespace Main {

static bool sNeedsRefresh = false;

void init() {
  Ion::Simulator::Display::init();
  setNeedsRefresh();
}

void setNeedsRefresh() {
  sNeedsRefresh = true;
}

void refresh() {
  if (!sNeedsRefresh) {
    return;
  }

  Display::draw();

  sNeedsRefresh = false;
}

void quit() {
  Ion::Simulator::Display::quit();
}

void EnableStatusArea(int opt) {
  __asm__ __volatile__ (
    ".align 2 \n\t"
    "mov.l 2f, r2 \n\t"
    "mov.l 1f, r0 \n\t"
    "jmp @r2 \n\t"
    "nop \n\t"
    ".align 2 \n\t"
    "1: \n\t"
    ".long 0x02B7 \n\t"
    ".align 4 \n\t"
    "2: \n\t"
    ".long 0x80020070 \n\t"
  );
}

extern "C" void *__GetVRAMAddress(void);

uint8_t xyram_backup[16 * 1024];
uint8_t ilram_backup[4 * 1024];

void worldSwitchHandler(void (*worldSwitchFunction)(), bool prepareVRAM) {
  // Back up XYRAM
  uint8_t* xyram = (uint8_t*) 0xe500e000;
  memcpy(xyram_backup, xyram, 16 * 1024);
  // Back up ILRAM
  uint8_t* ilram = (uint8_t*) 0xe5200000;
  memcpy(ilram_backup, ilram, 4 * 1024);

  if (prepareVRAM) {
    // Copying the screen to the OS's VRAM avoids a flicker when powering on
    uint16_t* dst = (uint16_t *) __GetVRAMAddress();
    uint16_t* src = gint_vram + 6;

    for (int y = 0; y < 216; y++, dst += 384, src += 396) {
      for (int x = 0; x < 384; x++) {
        dst[x] = src[x];
      }
    }

    // Disable the status area
    EnableStatusArea(3);
  }

  worldSwitchFunction();

  // Restore XYRAM
  memcpy(xyram, xyram_backup, 16 * 1024);
  // Restore ILRAM
  memcpy(ilram, ilram_backup, 4 * 1024);
}

void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) {

  gint_world_switch(GINT_CALL(save_state,storage_name));

  gint_world_switch(GINT_CALL(worldSwitchHandler, powerOffSafeFunction, prepareVRAM));
}

}
}
}
Thanks, I'll probably look at it later and try to add it to mine. If you want to add things without going through me, now that my PR has been merged into Upsilon you can also just submit PRs there if you want.

Quote:
3d and all numeric code inside KhiCAS is using the double type.


I guess that's for precision? That must slow it down a lot though, floats and especially doubles are painfully slow on this calculator... Also libgcc's floating point emulation (which I assume KhiCAS is using) is really slow, the docs themselves describe it as "a rather inefficient implementation of software floating point". I'm working on porting a floating point library that seems to be around 5x faster
Very interesting! Faster floats would indeed be welcome. I think I tried 3d code with float instead of double without noticeable speedup, but I'm not sure it was on the Casio, I'll give it a try again.

About Upsilon, I had to make another change to have the correct byte ordering for the scriptstore header (because of endianness). Then it should be possible to send a backup to a real Numworks and conversely, or extract the Python scripts (or conversely put Python scripts in a backup and send them to Upsilon fxcg). Correct code for load/save is below. I'm not familiar with git/PR/etc. I would probably mess up everything, it's much safer if you or the Upsilon team integrate the changes.

Code:

static int save_state(const char * fname){
  if (1 || Ion::Storage::sharedStorage()->numberOfRecords()){
    const char * ptr=(const char *)storage_address();
    // find store size
    int S=4;
    for (ptr+=4;;){ // skip header
      size_t L=ptr[1]*256+ptr[0];
      ptr+=L;
      S+=L;
      if (L==0) break;
    }
    S = ((S+1023)/1024)*1024;
    FILE * f=fopen(fname,"wb");
    if (f){
      ptr=(const char *)storage_address();
      fputc(ptr[3],f);
      fputc(ptr[2],f);
      fputc(ptr[1],f);
      fputc(ptr[0],f);
      fwrite(ptr+4,1,S-4,f);
      fclose(f);
      return S;
    }
    return 0;
  }
  return 2;
}

static int load_state(const char * fname){
  FILE * f=fopen(fname,"rb");
  if (f){
    char * ptr=(char *)storage_address();
    ptr[3]=fgetc(f);
    ptr[2]=fgetc(f);
    ptr[1]=fgetc(f);
    ptr[0]=fgetc(f);
    fread(ptr+4,1,storage_length,f);
    fclose(f);
    return 1;
  }
  return 0;


Heath wrote:
That must slow it down a lot though, floats and especially doubles are painfully slow on this calculator... Also libgcc's floating point emulation (which I assume KhiCAS is using) is really slow, the docs themselves describe it as "a rather inefficient implementation of software floating point".

You're right. Replacing double by float in 3d plots speeds up rendering by a factor about 1.7, many thanks!
I have updated controller.cpp and main.cpp in order to be able to exchange scripts from Upsilon to Casio and reverse (press VARS from Upsilon home menu).
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/controller.cpp
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/main.cpp
Addin:
https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/epsilon.g3a
May I recommend someone host the code on Github, so that contributions can be accepted via PR? It seems like multiple people are interested in providing code and/or feedback.
Technically, Parisse (and others) could also just use the github online editor to submit the PR via this link (for instance, it's just the "edit" link on a file) where it does all the job on its own (forks the projects and redirects you to a page where you can edit the code and save it ready to be submitted upstreamed), no need to mess with git and commits etc. If you don't want to (or don't know how to) Smile
It would indeed be nice to have all this there for other people to be able to contribute more easily!
KermMartian wrote:
May I recommend someone host the code on Github, so that contributions can be accepted via PR? It seems like multiple people are interested in providing code and/or feedback.


It has been upstreamed into Upsilon now so the code is at https://github.com/UpsilonNumworks/Upsilon
parisse wrote:
I'm not familiar with git/PR/etc. I would probably mess up everything, it's much safer if you or the Upsilon team integrate the changes.


I don't mind you submitting code like this but in general I think git is a really good skill to learn, it's not that hard and it's even useful for projects with one person as you can have a record of what you changed for future reference. If you don't have push permissions to a repo there's nothing you can do to mess it up. That's just a recommendation though, I don't mind how you do it
I had to update https://www-fourier.univ-grenoble-alpes.fr/~parisse/tmp/controller.cpp again (wrong type char * instead of unsigned char * for the storage, that messed up the sizes).
I apologize I'm not working with the same tools as most developpers now, but it's just too hard for me to learn new dev tools. I can make an issue in the Upsilon project with the source code if it's more convenient.
Don't worry, I do not expect to work on the port much, I just want it to have the functionnalities that my students will need, and that should be soon ok (I hope it's ok now).
This is very amazing. Also I'm glad that NumWorks did a good job at portability of their code and making it open-source for forking at one point to make Upsilon itself possible. What I like is how turning the calculator OFF while in epsilon seems to turn OFF like in Casio mode while still returning to Upsilon upon turning it back ON.

I noticed something weird about Python, though: To exit scripts you have to hold the EXIT key down for a few seconds it seems to register a keypress. The first time I tried Upsilon on my fx-CG10 I tried every single key one by one to exit a Python script, to no avail, due to myself not immediately realizing I had to hold the appropriate key down, so on my first try I just pressed the reset button on the back. I thought I'd let other people know in case they try running Python scripts on their fx-CG10/20/50/G90E.

I had very low expectations when it comes to speed, but I am used to TI-Z80/eZ80 speeds and so far I am quite impressed considering my calculator isn't overclocked right now.
I'm not sure if anyone else has had this issue, but I find that after being in Upsilon for a while, holding the MENU key gives a glimpse of the main menu but immediately returns to Upsilon, meaning that it cannot be exited without restarting.

This has happened a few times on my fx-CG 50, but I'm not sure exactly how to reproduce it apart from just staying in Upsilon and using the calculator and turning it on and off.
dr-carlos wrote:
I'm not sure if anyone else has had this issue, but I find that after being in Upsilon for a while, holding the MENU key gives a glimpse of the main menu but immediately returns to Upsilon, meaning that it cannot be exited without restarting.

This has happened a few times on my fx-CG 50, but I'm not sure exactly how to reproduce it apart from just staying in Upsilon and using the calculator and turning it on and off.


Yes, I’ve had this issue too. My theory is it happens after the calculator goes into some kind of deep sleep after being powered off for a long time, but it’s difficult to reproduce and fix when it takes so long
Heath wrote:
Yes, I’ve had this issue too. My theory is it happens after the calculator goes into some kind of deep sleep after being powered off for a long time, but it’s difficult to reproduce and fix when it takes so long

I think I found deterministic way to reproduce.
1. Start calculator (e.g. in CASIO run/mat mode)
2. Start Upsilon, goto calculation
3. Turn off calculator
4. Turn on calculator
5. hold [menu] and observe:
- Upsilon menu is shown
- CASIO menu is shown for a short
- Upsilon menu is shown
- CASIO menu is shown
MPoupe wrote:
Heath wrote:
Yes, I’ve had this issue too. My theory is it happens after the calculator goes into some kind of deep sleep after being powered off for a long time, but it’s difficult to reproduce and fix when it takes so long

I think I found deterministic way to reproduce.
1. Start calculator (e.g. in CASIO run/mat mode)
2. Start Upsilon, goto calculation
3. Turn off calculator
4. Turn on calculator
5. hold [menu] and observe:
- Upsilon menu is shown
- CASIO menu is shown for a short
- Upsilon menu is shown
- CASIO menu is shown


Thanks, though I’m not sure if it’s the same issue, but it is a starting point to see why going to the menu can fail
FYI, I also experienced the "trapped in Upsilon" issue. I have modified the home app, so that a normal DEL keypress leaves Upsilon. I have not experienced the issue since, I don't know if it's the long MENU keypress that is responsible or something else.
Other changes I made: support for Casio directories when exchanging files from Upsilon to Casio. I also changed the Python heap address to the unused memory area at 0x8c200000, size 0x300000, that's 48 times more memory for your Python scripts.
Addin https://www-fourier.univ-grenoble-alpes.fr/~parisse/casio/upsilon.g3a
The changes in source code:
apps/home/controller.cpp:
https://www-fourier.univ-grenoble-alpes.fr/~parisse/casio/controller.cpp
ion/src/simulator/fxcg/main.cpp
https://www-fourier.univ-grenoble-alpes.fr/~parisse/casio/main.cpp
apps/code/app.cpp: add a declaration at begin

Code:

#ifdef _FXCG
extern "C" int calculator;
#endif

(calculator is a variable that is set to 1 by main if the calc is a FXCG50 or the French equivalent mode Graph 90)
and modify

Code:

void App::initPythonWithUser(const void * pythonUser) {
  if (!m_pythonUser) {
#ifdef _FXCG
    if (calculator==1)
      MicroPython::init( (void *) 0x8c200000, (void *)(0x8c200000+ 0x300000));
    else
#endif
      MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize);
  }
  m_pythonUser = pythonUser;
}
Hi

As the end of the "extended" RAM is not blank, I would suggest to limit the allocation to 0x4e0000 in that region instead of 0x500000.

So use


Code:

void App::initPythonWithUser(const void * pythonUser) {
  if (!m_pythonUser) {
#ifdef _FXCG
    if (calculator==1)
      MicroPython::init( (void *) 0x8c200000, (void *)0x8c4e0000);
    else
#endif
      MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize);
  }
  m_pythonUser = pythonUser;
}


to avoid writing on non-zero zone.
This has been proven to work up to OS 3.80.1.

Cheers

Sly
Ok, that's not a big difference.
I'm also adding support to import Python scripts from Casio directory without requiring a copy in the Numworks scriptstore. This might be important for large projects, because the Numworks scriptstore has a capacity of 60 000 bytes in Upsilon. It can be raised to 64K, but not more, because field length is written on 2 bytes.
Modifications:
File python/src/py/reader.c: #if defined _FXCG || MICROPY_READER_POSIX

File python/port/port.cpp
add at top

Code:

#ifdef _FXCG
#include <gint/bfile.h>
#include <gint/display-cg.h>
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/keyboard.h>

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#endif


Modify

Code:

mp_lexer_t * mp_lexer_new_from_file(const char * filename) {
  if (sScriptProvider != nullptr) {
    const char * script = sScriptProvider->contentOfScript(filename, true);
    if (script != nullptr) {
      return mp_lexer_new_from_str_len(qstr_from_str(filename), script, strlen(script), 0 /* size_t free_len*/);
    }
  }
#ifdef _FXCG
  mp_reader_t reader;
  mp_reader_new_file(&reader, filename);
  return mp_lexer_new(qstr_from_str(filename), reader);
#endif
  mp_raise_OSError(MP_ENOENT);
}

mp_import_stat_t mp_import_stat(const char *path) {
  if (sScriptProvider && sScriptProvider->contentOfScript(path, false)) {
    return MP_IMPORT_STAT_FILE;
  }
#ifdef _FXCG
  FILE * f=fopen(path,"rb");
  if (f){
    fclose(f);
    return MP_IMPORT_STAT_FILE;
  }
#endif
  return MP_IMPORT_STAT_NO_EXIST;
}

All the files I modified are inside this archive https://www-fourier.univ-grenoble-alpes.fr/~parisse/casio/upsilon_changes.tgz
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 2 of 3
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement