Skip to content

Introduce "methodmaps".#38

Closed
dvander wants to merge 24 commits intomasterfrom
methodmaps
Closed

Introduce "methodmaps".#38
dvander wants to merge 24 commits intomasterfrom
methodmaps

Conversation

@dvander
Copy link
Member

@dvander dvander commented Jun 16, 2014

A few years ago Sawce suggested a way to introduce syntactic OO-sugar into our API. The idea was to introduce an x.y operator that would call native y if it could accept x as an implicit first parameter. At the time it wasn't clear to me how we could make this work. But now I think I have something. With this, we should be able to incrementally opt our users into an API that feels object-oriented. It's also a fairly powerful extension to the tag system, in that it doesn't require actual objects - for example, a "Player" tag could be used to make all of the player-based natives feel object-y.

This feature is a bit of a work in progress and should probably wait for 1.7 to branch. But it's working enough for review. Since there were a lot of indent-fails in the areas I had to touch, it might be best to review it commit by commit.

Anyway, methodmaps: they work like this.

methodmap Handle {
    Close = CloseHandle;
};

methodmap AdtArray < Handle {
    Clear = ClearArray;
    Clone = CloneArray;
    Size = GetArraySize;
    PushCell = PushArrayCell;
    Iterate = native GetArrayIterator(AdtArray:iter);
};

public main() {
    new AdtArray:array = CreateArray();
    array.PushCell(5);
    array.PushCell(10);
    array.Close();
}

A "methodmap" attaches a list of named functions onto a tag. When the . operator is used on a tagged scalar, the tag on the left-hand side is used to find an associated methodmap. The right-hand symbol is used to lookup the method. The left-hand side is magically passed as an implicit "this".

methodmaps can either point to an existing native, or an inline-declared native. This is because most APIs will need to support both calling conventions. For brand new APIs (like the transaction API), it seemed okay to just change the signatures to encourage methodmap use.

Methodmaps also support simple linear inheritance, and the type system has been extended to support that as well. For example, it is legal to assign a Transaction to a Handle as long as there is a methodmap Transaction < Handle {} somewhere.

Unfortunately, this is not expressive enough to just start porting random APIs over. The AdtArray example above assumes that we'd change the signatures of all the individual natives to take in AdtArray tags. That would break older code. I think what I'll have to do is introduce another inline syntax, like:

methodmap AdtArray < Handle {
    Size = stock (AdtArray:array) { return GetArraySize(array); }
}

Alternately, I could lax the rules for this a bit - so the first parameter can be the base type (right now it must be the derived type). Not sure yet.

@alliedmodders alliedmodders locked and limited conversation to collaborators Jun 16, 2014
@dvander
Copy link
Member Author

dvander commented Jun 17, 2014

After thinking about this some more - methodmaps are a great way to transition our older API in a backwards compatible way. To make that easier, I'm going to lax the type checking of |this|. It will make the AdtArray example work without having to change the type signature of older natives in an incompatible way.

A more interesting question is how to approach newer APIs. I think I've settled there that, full-fledged OO is too difficult within spcomp1. The hardest problem is that we'd need garbage collection, which in and of itself is not terrible, but to make it work well we need to know which values in the heap point to GC objects. That's a daunting task, especially since the Pawn assembler plays fast and loose with function frame organization.

We can take baby steps though. What I'll implement next is something similar to methodmaps, that we can use on newer APIs:

class Transaction
{
  public native Transaction();
  public native ~Transaction();
  public native AddQuery(const String:name[], any:data = 0);
};

This will desugar into a methodmap internally. The native bindings will be unreachable, hiding the C-like nature of the underlying implementation (they'll bind as something like Transaction.AddQuery instead). Construction will happen via a method call on Transaction, and closing the handle will happen via a delete keyword.

There are two major differences between this API and methodmaps. Methodmaps expose the underlying C-like API, and are attached to existing tags. Classes will expose no such API, and classes must have a totally unique tag. These tags cannot be coerced to anything they cannot be stripped, and they cannot flow into variadic lists or anything marked any:.

These restrictions ensure that we can someday add GC support, bit-by-bit, to old natives. Without those restrictions, pointer values could flow all throughout SM data structures and it'd be a nightmare to get GC working at all (ahem, bug 3966).

I'm tentatively thinking of ditching Handles entirely for classes. New functions - like the transaction API - don't need handles, and it would be really good to define the C++ object API ahead of time. If we can require that new APIs have proper rooting, plugging in GC later will require almost no work.

@alliedmodders alliedmodders unlocked this conversation Jun 17, 2014
@voided
Copy link
Contributor

voided commented Jun 17, 2014

Will methodmaps be limited to only tagged objects?

Cause for instance I can see this being useful for wrapping over all the entity/player APIs that pass around untagged entity indexes, such that something like this could exist:

methodmap Entity
{
    Teleport = TeleportEntity;
    GetClassname = GetEntityClassname;
    ...
}

methodmap Player < Entity
{
    GetName = GetClientName;
    ...
}

Additionally, I saw a comment in e3ddef8:

// Check the implicit this parameter. Currently we only allow scalars. As
// to not encourage enum-structs, we will not allow those either.

Is there a good reason against allowing enumstructs? I've grown pretty accustomed to using enumstructs in my own APIs any time I need to pass PODs around, or perform some actions on them, and it would be neat to be able to pass them around in methodmap functions instead of relying on old C-like functions.

@dvander
Copy link
Member Author

dvander commented Jun 17, 2014

Will methodmaps be limited to only tagged objects?

Methodmaps are usable with any tag, as long as the tag does not derive from a struct or class type (the latter is a pending construct). Indeed, psychonic mentioned a potential new methodmap-based entity API to unravel a lot of weirdness in the old one. It sounds like a good idea and that's exactly what methodmaps are there for: allowing us to transition to an OO API before having full OO support.

Is there a good reason against allowing enumstructs?

The technical reason is that methodmaps apply to tags. Tags are always scalar. Enum-structs are arrays. That is,

enum X {
    Y,
    Z,
};

In Pawn, this defines three constant symbols: Y=0, Z=1, and X=2. Declaring an enumstruct is allocating an array whose size is the highest enumeration value + 1. A methodmap over X can accept a Y or Z as this. To accept an X[], it would need to be a methodmap over X[] specifically. In theory we could make this work with some extra implementation effort - it doesn't look that hard.

On the other hand, enumstructs aren't really supported. I realize they happen to exist, and are used, but the instant we can provide something better they will be removed. They're offensive semantically and their implementation is awful.

@peace-maker
Copy link
Member

Methodmaps are usable with any tag, as long as the tag does not derive from a struct or class type (the latter is a pending construct).

Currently doing

methodmap Entity {
    IsValid = IsValidEntity;
};

results in
error 108: method must have a first argument compatible with the methodmap type (Entity)

And you can't write methodmap _ {}; methodmap Entity < _ { .. }; to allow no tag.

So the only way is to define wrapper stocks or some new anonymous inline function declaration syntax you propsed in your first comment.

stock bool:IsValidEntityW(Entity:entity)
{
    return IsValidEntity(_:entity);
}

methodmap Entity {
    IsValid = IsValidEntityW;
};

vs.

methodmap Entity {
    IsValid = stock bool:(Entity:entity) { return IsValidEntity(_:entity); }
};

I'd encourage the second one to avoid cluttering. This would also be usable for a ClientCookie methodmap. The cookie Handle is passed as second argument in the GetClientCookie functions, so you need to switch the arguments around to have it work as an implicit this.

@dvander
Copy link
Member Author

dvander commented Jun 18, 2014

@peace-maker Yes, that is the intended behavior.

@dvander
Copy link
Member Author

dvander commented Jun 18, 2014

We'll see inline methods a few PRs later. The syntax will look like:

methodmap X {
    public bool:Function() {
    }

@dvander
Copy link
Member Author

dvander commented Jun 20, 2014

I just pushed a major refactoring of this PR to begin implementing the planned Transitional Syntax. This patch now introduces:

  • Methodmaps, with new syntax that looks more similar to declaring a C++ class.
  • Destructors and a "delete" keyword to normalize behavior across Handles and to-be-implemented Objects.
  • Constructors, a suggestion from asherkin - constructors inherit the name of the methodmap.
  • The beginnings of the new declaration style - that I'll fill out more in another patch.

What's left to do on methodmaps: more tests, support for inline declarations, properties.

In addition, to get this stuff working I did some bugfixing/refactoring within spcomp:

  • sc.h now longer has that horrid hardcoded token list.
  • the lex+tokeninfo pattern is replaced with lextok() in new code, which combines the two operations.
  • there is a new symbol type called "proxies" that let one symbol redirect to another. this is what makes constructors work.
  • there's a little compiler test harness now.
  • there was a bug in line number computation for error reporting that is fixed.

dvander added a commit that referenced this pull request Jun 21, 2014
commit 1e5213d
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 04:09:27 2014 -0700

    Quell MSVC C99 bugs.

commit f2e166c
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:59:23 2014 -0700

    Fix varying levels of stupid memory errors.

commit b0773d7
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:36:39 2014 -0700

    Fix memory leak in parsing some control flow structures.

commit 5aca557
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:35:17 2014 -0700

    Fix memory leak in struct parsing.

commit b46ec5c
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:32:03 2014 -0700

    Fix build.

commit 17bbbb9
Merge: c083409 2107599
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 01:26:27 2014 -0700

    Merge branch 'master' into methodmaps

commit c083409
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 23:49:36 2014 -0700

    Add VS2k13 support.

commit b799377
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 01:28:08 2014 -0700

    Implement destructors.

commit 1a340de
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 00:08:04 2014 -0700

    Add some tests.

commit 12db52e
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 00:05:49 2014 -0700

    Initial implementation of constructors.

commit 074669a
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:42:35 2014 -0700

    Add simple test harness.

commit 27c1e3c
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:15:42 2014 -0700

    Big refactoring for new syntax.

commit f3c37fd
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:12:54 2014 -0700

    Refactor tests for the new syntax.

commit 6211f39
Author: David Anderson <[email protected]>
Date:   Wed Jun 18 22:25:48 2014 -0700

    Make lexer tokens an enum.

commit 5210b01
Author: David Anderson <[email protected]>
Date:   Tue Jun 17 06:48:15 2014 -0700

    Add comment.

commit 06688ff
Author: David Anderson <[email protected]>
Date:   Tue Jun 17 06:46:10 2014 -0700

    Allow |this| to be a base type of the methodmap.

commit 05cf368
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 22:11:58 2014 -0700

    Unify duplicate typesymbol checking.

commit 09161bf
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 19:53:36 2014 -0700

    Close loophole that allowed methodmaps for enums.

commit 5bb4aeb
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:50:42 2014 -0700

    Add tests and dbi/handle changes.

commit b9203e2
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:38:29 2014 -0700

    Ensure methodmap tags are fixed.

commit 878b80f
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:36:04 2014 -0700

    Implement inheritance.

commit 6ba9e00
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:31:00 2014 -0700

    Refactor matchtag() to not be insane.

commit 4ede634
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:20:50 2014 -0700

    Fix indenting.

commit e3ddef8
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:20:27 2014 -0700

    Initial prototype.
@dvander
Copy link
Member Author

dvander commented Jun 21, 2014

Merged via command-line.

@dvander dvander closed this Jun 21, 2014
@dvander dvander deleted the methodmaps branch June 21, 2014 19:02
FlaminSarge pushed a commit to FlaminSarge/sourcemod that referenced this pull request Jun 23, 2014
commit 1e5213d
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 04:09:27 2014 -0700

    Quell MSVC C99 bugs.

commit f2e166c
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:59:23 2014 -0700

    Fix varying levels of stupid memory errors.

commit b0773d7
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:36:39 2014 -0700

    Fix memory leak in parsing some control flow structures.

commit 5aca557
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:35:17 2014 -0700

    Fix memory leak in struct parsing.

commit b46ec5c
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 03:32:03 2014 -0700

    Fix build.

commit 17bbbb9
Merge: c083409 2107599
Author: David Anderson <[email protected]>
Date:   Sat Jun 21 01:26:27 2014 -0700

    Merge branch 'master' into methodmaps

commit c083409
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 23:49:36 2014 -0700

    Add VS2k13 support.

commit b799377
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 01:28:08 2014 -0700

    Implement destructors.

commit 1a340de
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 00:08:04 2014 -0700

    Add some tests.

commit 12db52e
Author: David Anderson <[email protected]>
Date:   Fri Jun 20 00:05:49 2014 -0700

    Initial implementation of constructors.

commit 074669a
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:42:35 2014 -0700

    Add simple test harness.

commit 27c1e3c
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:15:42 2014 -0700

    Big refactoring for new syntax.

commit f3c37fd
Author: David Anderson <[email protected]>
Date:   Thu Jun 19 22:12:54 2014 -0700

    Refactor tests for the new syntax.

commit 6211f39
Author: David Anderson <[email protected]>
Date:   Wed Jun 18 22:25:48 2014 -0700

    Make lexer tokens an enum.

commit 5210b01
Author: David Anderson <[email protected]>
Date:   Tue Jun 17 06:48:15 2014 -0700

    Add comment.

commit 06688ff
Author: David Anderson <[email protected]>
Date:   Tue Jun 17 06:46:10 2014 -0700

    Allow |this| to be a base type of the methodmap.

commit 05cf368
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 22:11:58 2014 -0700

    Unify duplicate typesymbol checking.

commit 09161bf
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 19:53:36 2014 -0700

    Close loophole that allowed methodmaps for enums.

commit 5bb4aeb
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:50:42 2014 -0700

    Add tests and dbi/handle changes.

commit b9203e2
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:38:29 2014 -0700

    Ensure methodmap tags are fixed.

commit 878b80f
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:36:04 2014 -0700

    Implement inheritance.

commit 6ba9e00
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:31:00 2014 -0700

    Refactor matchtag() to not be insane.

commit 4ede634
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:20:50 2014 -0700

    Fix indenting.

commit e3ddef8
Author: David Anderson <[email protected]>
Date:   Mon Jun 16 01:20:27 2014 -0700

    Initial prototype.
dvander added a commit that referenced this pull request Nov 4, 2015
Remove a bunch of unsupported junk.
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

Successfully merging this pull request may close these issues.

3 participants