Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Chris
    @cmidgley

    Also - found a small mistake in Timer.d.ts. The set and schedule methods have an optional repeat parameter. In the typings it is stated as boolean yet the implementation is actually number. Changes needed to lines 28 and 36 from:

          repeat?: boolean

    to

          repeat?: number
    Chris
    @cmidgley

    I'm finding a couple odd behaviors with workers. First, the debugger call doesn't appear to break into the debugger on a worker thread. Using examples\base\worker (using mcconfig -m -d -p win), I simply added debugger; to simpleworker.js in onmessage yet it does not break into the debugger.

    let counter = 0
    
    self.onmessage = function(msg) {
        debugger;
        self.postMessage({hello: "from simple worker", counter: ++counter});
    }

    Likewise, if I add a trace call, the message does not appear.

    let counter = 0
    
    self.onmessage = function(msg) {
        trace("Inside worker\n");
        self.postMessage({hello: "from simple worker", counter: ++counter});
    }

    Are either of these limited to work only in the main thread and not a worker?

    Peter Hoddie
    @phoddie
    worker.gif
    @cmidgley - debugging of workers is supported. See the above screen recording of an ESP32.
    It does seem to be broken on the simulator at the moment. Perhaps that is what you are seeing? If so, please file an issue on GitHub with the details and we'll take care of it. Thanks.
    Chris
    @cmidgley
    Yes, problem in simulator. Will file an issue. Want one issue for both trace and debugger, or separate them?
    Peter Hoddie
    @phoddie
    One issue is enough. It is almost certainly the same root cause.
    Peter Hoddie
    @phoddie
    Regarding the TypeScript exports, I understand what you are saying. I don't know what the usual TypeScript solution is there. As I'm very new to TypeScript, I'd prefer to follow a precedent. The original Timer declarations are from @bmeck. He may have some guidance. The Moddable SDK Timer object is similar to setTimeout and friends in the browser runtime. Maybe you can track down their declarations to see if that offers some insight. (They should be somewhere in the TypeScript repository on GitHub.)
    Chris
    @cmidgley

    It's a bit over my head ... but in node_modules/@types/node/timer.d.ts is says:

    declare module "timers" {
        function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;
        (... truncated ...)

    It is also defined in globals.d.ts as:

    declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;

    And also in globals.d.ts it has:

    declare namespace NodeJS {
        (... truncated ...)
        interface Timeout extends Timer {
            hasRef(): boolean;
            refresh(): this;
            [Symbol.toPrimitive](): number;
        }
        (... truncated ...)

    Perhaps NodeJS namespace is the default namespace for global? In which case the interface is included there, rather than in the timer.d.tsmodule, thereby making it globally available?

    I'm afraid I don't follow all this... hoping this might be more helpful to you!

    Peter Hoddie
    @phoddie
    I don't follow it all either. ;) I just keep exploring. I came up with something simpler, that I believe gives the result you want. The Timer is not an ID, but an instance. That suggests that the declaration can be a class, albeit one without an invokable constructor.
    declare module "timer" {
      type TimerCallback = (timer?: Timer) => void;
    
      class Timer {
        private constructor()
        static set: (
          callback: TimerCallback,
          interval?: number,
          repeat?: number
        ) => Timer
        static repeat: (
          callback: TimerCallback,
          interval: number,
        ) => Timer
        static schedule: (
          timer: Timer,
          interval?: number,
          repeat?: number
        ) => void;
        static clear: (timer: Timer) => void;
        static delay: (milliseconds: number) => void;
    
        private brand: boolean;
      }
    
      export {Timer as default};
    }
    That way you don't need to do any extra imports and you can just user Timer in your code:
    function repeat(id: Timer)
    {
        trace(`repeat ${count} \n`);
        if (5 == ++count)
            Timer.clear(id);
    }
    Chris
    @cmidgley

    Interesting ... so using the class of Timer as both the class, and as the resulting identifier. I would have thought the result of the set() method to be a unique type, not the Timer class itself (such as the ts code above, which uses the type Timeout). Perhaps because Timeout just extends Timer (according to the setTimeout ts declaration) maybe this simplification is ok?

    But given this knowledge, how does this help for example with Self in Worker (and I would guess other modules that have native types)?

    Peter Hoddie
    @phoddie
    Is there a case that this declaration for Timer handles incorrectly?
    I understand timer. I don't understand what you are trying to do with Self. You can't subclass self. What's the problem?
    Chris
    @cmidgley
    No - this declaration technically works for Timers (and I've deployed it and it works great!) - it just seems odd to me that's all. And I guess as long as Self remains a single use global, for Worker only, then it's not an issue. Maybe this is all just me trying to wrap my head around TypeScript! All is good right now - thanks as always for your willingness to share your time, and with a fantastic attitude to boot. I'm hoping the energy you are investing in me right now will pay back for you in the coming year... Happy New Year!
    Peter Hoddie
    @phoddie
    Happy to try to help. I appreciate your patience in working through things.
    On your MyWorker example, the class is only instantiated once per VM, so maybe it is enough to use some static methods? Since self is in the globals, it doesn't really need to be passed. So maybe something like this?
    class MyWorker {
        static initialize(void): void {
            self.onmessage = MyWorker.handleMessage;
        }
        static handleMessage(message:Object):void {
            // do work...
        }
    }
    
    MyWorker.initialize();
    Chris
    @cmidgley
    Yes, though I do have an allergic reaction in general to static (try to avoid it) but it does have it's use. It's a religious debate in the end....!
    I see a lot of work on the new IO modules - my interest at the moment is focused on Serial which appears to have Linux, Mac and Windows implementations, as well as ESP8266 (but maybe not ESP32, not sure). However, the IO manifest seems to support just ESP. Is this something I should ignore, or start to play with?
    Peter Hoddie
    @phoddie
    For ESP8266, the implementation in modules/io is good. That's the TC53 API, which i s eventually where we'll move everything. It has experimental support on Mac Windows, and Linux too. ESP32 is planned as part of a bigger effort to bring TC53 API to ESP32.
    Chris
    @cmidgley
    Thanks - it looks like a much better interface, glad you are working on it. Do you happen to know if Serial is somewhat close on lin/win/mac? If not, I'll be rolling my own (using a third party serial library for lin/mac/win), so don't mind helping the project vs. start from scratch. I must implement my own on ESP32 no matter what as I'm using a non-standard USB chip (I need to operate as a USB host which can't be done with FTDI) - doubt that will be useful to the community though as it's a custom hardware platform.
    Peter Hoddie
    @phoddie

    Do you happen to know if Serial is somewhat close on lin/win/mac? I

    It works well for my uses. But it has not been exercised thoroughly.

    Chris
    @cmidgley
    Thanks - will start looking into that ... next year!
    Chris
    @cmidgley

    @phoddie I think I finally found what I was looking for in TypeScript ... types / interfaces / etc that you want available from a module, but without exporting/importing them by name, can be put into the global namespace. For example, here is serial.d.ts for the new IO serial class:

    declare module "builtin/serial" {
        global {
            type SerialConfiguration = {
                device?: string,
                baud?: number,
                target?: any, 
                format?: string,
                onError?(): void,
                onReadable?(count:number): any,
                onWritable?(count:number): any
            };
    
            type SerialSetOptions = {
                RTS:boolean, 
                DTS:boolean
            };        
        }
        class Serial  {
            constructor(options: SerialConfiguration);
            check(): void;
            close(): void;
            read(): ArrayBuffer | String;
            write(data:ArrayBuffer | String): void;
            set(options:SerialSetOptions):void;
            get format():string;
            set format(mode:string);
            purge():void;
        }
    
        export {Serial as default};
    }

    This can then be used simply as:

    import Serial from "builtin/serial";

    And yet you can still directly use the types SerialConfiguration and SerialSetOptions, such as:

    let config:SerialConfiguration = {
        device: "COM3", 
        baud: 115200
    };

    Perhaps you knew this - but this is exactly what I was trying to find. A way to keep my types defined with the module, and contain multiple interfaces/types that can be used on primary class.

    Chris
    @cmidgley
    Here is what timer.d.ts1 might be, reverting to your prior version that usedTimerID` but now using the global namespace (including both TimerID and TimerCallback, as the callback also should be known externally):
    declare module "timer" {
      global {
        type TimerCallback = (timer?: TimerID) => void;
        type TimerID = number;
      }
      class Timer {
        private constructor()
        static set: (
          callback: TimerCallback,
          interval?: number,
          repeat?: number
        ) => TimerID
        static repeat: (
          callback: TimerCallback,
          interval: number,
        ) => TimerID
        static schedule: (
          timer: TimerID,
          interval?: number,
          repeat?: number
        ) => void;
        static clear: (timer: TimerID) => void;
        static delay: (milliseconds: number) => void;
    
        private brand: boolean;
      }
    
      export {Timer as default};
    }
    Peter Hoddie
    @phoddie

    Scaled across many modules, that's going to be a lot of global types! Doesn't so much clutter in global confuse more than help? I know the web runtime includes a staggering number of globals. Maybe that is what is done for types too?

    Setting that aside, a timer is an instance, not a number. Your proposed declaration allow any number to be passed as a TimerID. For example, this is not flagged as an error Timer.clear(12). The declaration I posted ensures that only a timer instance would be accepted as a parameter to the functions. (That's part of why the private brand is present in my version and is, I believe, unnecessary in yours.)

    Chris
    @cmidgley

    I'd have to look at the Timer module implementation to full grasp your point about instance vs. number - I was (I guess incorrectly) assuming it was implemented similar to setInterval (which does return a number). If Timer.set returns some instance (specifically, if it returns Timer) then totally understood.

    But as a more general point, if you look at the global types in the Typescript definition, you will find there are a ton, which generally makes sense as many types are global in nature. In the case of Timer, even if Set does return an instance of the Timer object, then what about the type TimerCallback? Without global, then something like TimerCallback would not be available for TS validation. There are many other examples of this challenge where a module needs to present multiple types/interfaces to correctly consume the module.

    Chris
    @cmidgley

    Further reading on this topic seems to indicate that namespaces can be used, but when they are system library services (as in this case with Timer from moddable base) they seem to be in global to avoid the extra mynamespace.mytype syntax. Individual authors, especially library authors, may well want to use namespaces to avoid naming collisions - so one could argue that Moddable should have it's own namespace. I'm not a fan of this due to the extra required syntax (and it appears neither was NodeJS, as it seems to provide both global and NodeJS namespaces for typing... though I'm still not clear on what they are doing).

    But do keep in mind... I'm a total novice at Typescript and no expert at Javascript. I have far more experience with more traditional OOP languages (C#, C++, Java, etc) and applying that experience to TS/JS isn't necessarily the right thing to do!

    Peter Hoddie
    @phoddie

    @cmidgley, even if a Timer was a number, that's an optimization: there's no practical reason to use it as a number. The declaration I wrote ensures a script can't accidentally pass a number as an argument that expects a Timer. That seems consistent with the overall goal of TypeScript.

    Regarding the exporting a type for the callback, I tried to find a precedent. I looked to sort on Array. Here's the definition from TypeScript:

    sort(compareFn?: (a: T, b: T) => number): this;

    TypeScript does not declare a comparison function type. It repeats the above declaration on Array and each TypedArray (there are a lot!).

    I hesitate to consider his a shortcoming in the TypeScript declarations. The TypeScript team could have declared a common type easily enough if that was considered important. I suspect it gets back to the fundamental differences between TypeScript and traditional strongly typed languages, like those you mentioned. Anyway, my goal is only to try to follow the common practices in TypeScript for our declarations. If there's a more appropriate precedent to follow, I'm all ears.

    Chris
    @cmidgley

    I feel like I missing something obvious here... I looked around and tried to find a better example of the problem I'm experiencing, which I hope is more clear here using WiFi.

    The following fragment using wifi.d.ts works with valid type checking on WiFiOptions (including detecting missing or invalid members):

    import WiFi from "wifi";
    let myWiFi:WiFi = new WiFi({ssid: "test"}, null);

    Yet the following says "error TS2304: Cannot find name 'WiFiOptions'." on the second line:

    import WiFi from "wifi";
    let options:WiFiOptions = {ssid: "test"};
    let myWiFi:WiFi = new WiFi(options, null);

    How do I reference the type WiFiOptions when it is not exported? It is known when I use it in the WiFi constructor, but not as a separate type. Perhaps something is wrong in my build environment? Both VS Code intellisense and running mcconfig generate the same error.

    If I modify wifi.d.ts to export type WiFiOptions and then import with import WiFi, { WiFiOptions } from "wifi"; then it works fine but that means a lot of importing of types - which is what I was implying earlier, so perhaps I'm just doing this all wrong?

    Peter Hoddie
    @phoddie
    Aren't you declaring types unnecessarily? TypeScript will infer that myWiFi is of type WiFi. Similarly, without declaring the type on options if the value is incompatible, TypeScript will tell you that when you pass it to the WiFi constructor. I understand that you don't get quite as strict checking, but... this seems to be how the TypeScript definitions for JavaScript work. Maybe that's their intent? What you are doing is trying to behave like C++ or Java. (I'm taking no position on what is better or correct. I don't have a strong opinion. I'm reflecting what I've taken from reviewing Microsoft's built-in declarations and reading their documentation. )
    Peter Hoddie
    @phoddie

    FWIW - I find this page on TypeScript's Type Compatibilty pretty helpful in understanding the big-picture approach and how it differs from strongly typed languages like C++ and Java. On the Modules page there's a brief note on Importing types which seems very relevant to what you are doing. It allows a module to export a type in a way that is explicitly independent from the JavaScript exports. I think that's relevant here for a couple reasons. First, by keeping the types export separate from the JavaScript language exports, there's no suggestion that they are part of the JavaScript API. Secondly, they are imported from the module rather than falling into the abyss of the giant global namespace. That brief text links to a longer description which explains this much better than I can here.

    So.... maybe the solution is to export the types from the modules (such as WiFiOptions above). Code that wants to explicitly declare variables with those types can import them explicitly. Code that does not want to be that explicit with types, can omit the import of the types. That keeps the simple case simple while allowing for the behavior that (I think I understand) you want.

    Closer? ;)

    Chris
    @cmidgley
    Export does feel like the right compromise. Without that, you can not delegate functionality (with strong typing) away from the inferred use (as soon as you leave the confines of the inferred type you are no longer able to use strong typing).
    Peter Hoddie
    @phoddie
    Agreed. Let's try that. I'll add some type exports in a few places -- Timer and Wi-FI seem like good starting points.
    Peter Hoddie
    @phoddie
    I added type exports for Timer, Wi-Fi, http, websocket, and socket. I hope they are useful. The style is still evolving. Let's see how these are to use. From there we can refine the approach or apply it more broadly.
    Peter Hoddie
    @phoddie
    Chris
    @cmidgley
    I have merged in the updates, and the typings look good to me. I was able to remove my custom typings and things are building for me, though I don't use them all yet. I also am running tests on serial right now, but on code inspection it looks great - changes make perfect sense and are a good improvement.
    Peter Hoddie
    @phoddie
    Excellent. Thanks for the quick feedback.
    Chris
    @cmidgley
    @phoddie I see some references to x-ios and x-ios-simulator, but no implementation of the platform itself. Is this something under consideration, or perhaps it hit some roadblock (like Apple's ios limitations on allowing scripting languages)? My use case is to embed it into a ios app (actually, in Unity) so I can share the scripts. It's not critical, as I can also run it in a server and remote the interface but wanted to see if you had any insight before proceeding with the design.
    Peter Hoddie
    @phoddie
    We have done work on iOS and Android, as you see in the mcconfig sources. During our sojourn at Marvell we shipped mobile products for both. They worked really well -- lighter and faster than just about anything. But, there are many, many ways to build apps for mobile. The opening for one more is limited. Our focus is microcontrollers.
    Chris
    @cmidgley
    It appears you can not use TLS listeners yet, and therefore no support for HTTPS when running as a server. Is that correct? If so, considering how security is top-of-mind on IOT devices these days, are there any plans to consider adding such capabilities?
    Peter Hoddie
    @phoddie
    Consider, yes. But it really isn't that simple given that HTTPS to an IP address doesn't work with public certificates.
    (e.g. you cannot establish a secure connection from an arbitrary browser to your local device, even if there's a full featured TLS stack present)
    Chris
    @cmidgley
    Yes, for a browser to generically access the XS HTTPS server would present several challenges (DNS names, certificates with accepted authorities, etc). But for device-to-device communications, or for app-to-device communications, then it presents substantial value (as you can use private certificates so not much overhead in terms of storing the certs themselves) - and that's my use case (XS to XS, NodeJS to XS, and device-native-app (iOS, Mac, Linux, Windows, Android) to XS. But it doesn't sound like it's in any near-term backlog, so don't plan on it for now?
    Peter Hoddie
    @phoddie
    No, it is not on the immediate horizon. It is possible. It would be great to have. But it isn't that widely useful and it is not trivial to implement.
    Chris
    @cmidgley
    Makes sense - thanks.
    Peter Hoddie
    @phoddie
    FWIW - if you read the TLS code carefully, you will see that there is more-or-less already support present for what you describe. But it hasn't been maintained and so the bit-rot will need to be resolevd.
    Peter Hoddie
    @phoddie
    We just pushed a preview of upcoming Moddable SDK improvements for ESP32 developers. Key points:
    • ESP-IDF 4.2 support
    • ESP32-S2 silicon support
    • Kaluga and Saola dev board build targets
    • Support for external PSRAM
    • Faster SPI output for faster rendering
      The branch is available to try now. Lots of details in this blog post.
    We plan to merge this to the main branch in early February. Please give it try before then to help shake out any issues or oversights. Thank you!
    Skye Sweeney
    @SkyeSweeney
    Is there a place to ask basic questions on my newly acquired Moddable_Two? This seems to be much more are the development level of the tools and not the "Why do I get this error?" level. Perhaps a Google group?